Walk Directory Tree and Collect Files in Zig

Recursively scan a directory and collect file paths that match a predicate.

This recipe was written using Zig version 0.15.2. If this recipe is outdated, please let me know on X (@sepyke) or Bluesky (@pyk.sh).

Recently, I was building a simple library and needed to list all Zig files in an examples directory to build them as binaries in my build.zig. This recipe shows how to recursively scan a directory and collect file paths that match a predicate using std.fs.Dir.walk().

First, you need to get a directory handle for your starting point. The most common way is to use std.fs.cwd() for the current working directory, and then openDir() to get a handle to your target directory (e.g. examples). It’s crucial to remember to set .iterate = true in OpenOptions when opening the directory, as this enables the directory to be scanned for its contents.

The Dir.walk() function returns a Walker, which is an iterator that goes through all entries (files and subdirectories) in a directory tree, including all its subdirectories. This is perfect for finding all .zig files under a specific path.

Here is the code snippet with the inline comments:

walk_directory_tree.zig
const std = @import("std");
const fs = std.fs;
const mem = std.mem;
const Allocator = mem.Allocator;

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    // In a real `build.zig`, `fs.cwd()` would typically be your project root.
    // For this demonstration, we'll use a temporary directory to keep things clean.
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup(); // Clean up the temporary directory when done.
    const base_dir = tmp.dir;

    // Create the 'examples' directory and some files inside it for testing.
    try base_dir.makeDir("examples");
    try base_dir.writeFile(.{ .sub_path = "examples/main.zig", .data = "const a = 1;" });
    try base_dir.makePath("examples/utils"); // Create a subdirectory
    try base_dir.writeFile(.{ .sub_path = "examples/utils/helper.zig", .data = "const b = 2;" });
    try base_dir.writeFile(.{ .sub_path = "examples/README.md", .data = "docs" }); // A non-Zig file

    const sub_dir_to_scan = "examples";
    std.debug.print("Searching for .zig files recursively in subdirectory: '{s}'\n", .{sub_dir_to_scan});

    // Open the target subdirectory with iteration enabled.
    // The `.iterate = true` option is essential for `walk()` to function.
    var target_dir = try base_dir.openDir(sub_dir_to_scan, .{
        .iterate = true,
    });
    defer target_dir.close(); // Always remember to close directory handles!

    // The walker recursively visits every file and directory.
    var walker = try target_dir.walk(allocator);
    defer walker.deinit(); // Deinitialize the walker to release resources

    std.debug.print("Found files:\n", .{});
    while (try walker.next()) |entry| {
        // We only care about files that end with ".zig".
        // `entry.kind == .file` checks if it's a file, not a directory.
        if (entry.kind == .file and mem.endsWith(u8, entry.path, ".zig")) {
            // Important: The memory for `entry.path` is managed by the walker
            // and will be reused on the next call to `next()`.
            // If you need to store the path, you *must* make a copy.
            std.debug.print("file: {s}\n", .{entry.path});
        }
    }
}

Make sure the zig is intalled:

Shell
$ zig version
0.15.2

Then run it:

Shell
$ zig run walk_directory_tree.zig
Searching for .zig files recursively in subdirectory: 'examples'
Found files:
file: utils/helper.zig
file: main.zig

Other recipes in File System