Directories
Getting the current directory in Zig
This data type can be useful for different tasks. It’s good to know which directory we’re in at any given time and act accordingly.
get_current_dir.zig |
|
const std = @import("std"); const print = std.debug.print; pub fn main() void { // get data for the cwd (current working dir) const o_cwd = std.fs.cwd(); // prepare a buffer to hold the response var a_buff: [std.fs.max_path_bytes]u8 = undefined; // get the real path of "." (current dir) and handle any error if (o_cwd.realpath(".", &a_buff)) |s_path| { print("Path {s}\n", .{s_path}); } else |err_x| { print("Error {any}\n", .{err_x}); } } |
|
$ zig run get_current_dir.zig |
|
Path /home/zenofzig/code/9 |
We’re using an array as a buffer to store the response from the realpath function:
|
// prepare a buffer to hold the response var a_buff: [std.fs.max_path_bytes]u8 = undefined; |
The maximum length that a path name can use is defined in std.fs.max_path_bytes and depends on the platform.
As an alternative to the realpath function, we can use realpathAlloc, which requires a memory allocator.
get_current_dir_alloc.zig |
|
const std = @import("std"); const print = std.debug.print; pub fn main() !void { // get data for the cwd (current working dir) const o_cwd = std.fs.cwd(); const o_alloc = std.heap.page_allocator; // get the real path of "." (current dir) and handle any error if (o_cwd.realpathAlloc(o_alloc, ".")) |s_path| { // free the memory of s_path // even though in this linear program // it would be freed at the end of main defer o_alloc.free(s_path); print("Path {s}\n", .{s_path}); } else |err_x| { print("Error {any}\n", .{err_x}); } } |
|
$ zig run get_current_dir_alloc.zig |
|
Path /home/zenofzig/code/9 |
Check directory access
If we just want to know whether a directory exists and we have access to it, we could use something like this:
|
check_dir.zig |
|
const std = @import("std"); const print = std.debug.print; fn check_dir(s_dir: []const u8) !void { var o_dir = try std.fs.openDirAbsolute( s_dir, .{}, ); defer o_dir.close(); try o_dir.access(".", .{}); } pub fn main() void { const s_dir = "/root/"; const s_msg = if (check_dir(s_dir)) "Exists" else |err_x| @errorName(err_x); print("{s}\n", .{s_msg}); } |
|
$ zig run check_dir.zig |
|
AccessDenied |
Iterate over a directory
In the following example, we go through the entries of a directory and print them. We use the function std.fs.Dir.iterate or iterateAssumeFirstIteration. The latter is used when we know we haven’t iterated over the directory before. For example, if this is the first iteration, it’s because we just opened the directory.
The key point is that these functions return an iterator.
We use the iterator i_dir by calling its .next() method to move to the next entry in the directory, if one exists.
It’s a perfect case to list directory contents using our trusty while loop:
|
list_dir.zig |
|
const std = @import("std"); const print = std.debug.print; pub fn main() !void { const s_dir = "/"; // open the directory var o_dir = std.fs.openDirAbsolute( s_dir, // flag needed to iterate over the dir .{ .iterate = true }, ) catch |err_x| { // if it doesn't exist or another error occurs: print("Error: {s} \n", .{@errorName(err_x)}); return; }; // close the dir defer o_dir.close(); // iterator to go through directory entries var i_dir = o_dir.iterateAssumeFirstIteration(); // while there are entries, print them while (try i_dir.next()) |o_entry| { print("{s}\n", .{o_entry.name}); } } |
|
$ zig run list_dir.zig |
|
srv dev tmp ... |
When we open the directory, to be able to create an iterator, we need to explicitly set the .iterate = true flag in the second argument.
You might be wondering: how do I know which flag to set to be able to iterate? And how can I find out other details about a function or object?
Earlier in the book, we saw how to do this with .empty:
https://zenofzig.com/libro/c7-listas-dinamicas-arraylist.html. To avoid repeating myself and keep it short: in all languages, it’s important to check the documentation and the source code.
If you look at the declaration of the openDirAbsolute function, you’ll see this:
fs.zig
|
pub fn openDirAbsolute( absolute_path: []const u8, flags: Dir.OpenOptions) File.OpenError!Dir { // ... |
Notice those flags: Dir.OpenOptions
Dir.zig
|
pub const OpenOptions = struct { /// ... /// `true` means the opened directory /// can be scanned for the files and sub-directories /// of the result. It means the `iterate` function can be called. iterate: bool = false, /// ... }; |
This documentation explains that to iterate over the entries of a directory, you must set .iterate = true when opening it.
Also if you omit this flag, the program will crash with this message:
|
.BADF => unreachable, // Dir is invalid or was opened without iteration ability |
All I can recommend is: check the functions you’re going to use and read their documentation, even just briefly. When working with files and directories, be careful: don’t accidentally delete or overwrite something important. Another tip: while you’re learning, do your tests in the /tmp/ directory.
Create and delete a directory
WARNING: If you run code in the zenofzig.com playgrounds, there’s no risk of deleting anything important. But if you run code that deletes files or directories on your own machine (not in the browser), make sure you understand exactly what it does before running it. You could potentially delete something important, so be careful.
To create a new directory at an absolute path:
|
create_dir.zig |
|
const std = @import("std"); const print = std.debug.print; pub fn main() !void { // create a dir at an absolute path try std.fs.makeDirAbsolute("/tmp/newdir"); } |
|
$ zig run create_dir.zig |
|
... |
To delete that same directory, use:
|
// delete a dir try std.fs.deleteDirAbsolute("/tmp/newdir"); |