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:
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:
$ zig version
0.15.2Then run it:
$ zig run walk_directory_tree.zig
Searching for .zig files recursively in subdirectory: 'examples'
Found files:
file: utils/helper.zig
file: main.zig