In C# I have the System.IO.Path.GetFullPath()
method, like this:
var path = System.IO.Path.GetFullPath("../../baz/file.txt", "/my/root/dir");
// path = /my/baz/file.txt
This builds a new absolute path based on an absolute base path and a relative path. It doesn’t matter if the path doesn’t exist. What is the equivalent in Zig?
I tried std.fs.Dir.realpath()
but it raises an error if the file doesn’t exist in the file system. The documentation in std.fs.path.resolve()
says it doesn’t work with relative paths.
It looks like GetFullPath
has an implicit “get the current working directory path as the basis for the absolute path”, so in Zig you’d have to do that separately:
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok);
const allocator = gpa.allocator();
// could also use std.os.getcwd()
const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(cwd_path);
std.debug.print("cwd_path: {s}\n", .{cwd_path});
const absolute_path = try std.fs.path.resolve(allocator, &.{
cwd_path,
"../../baz/file.txt",
});
defer allocator.free(absolute_path);
std.debug.print("absolute_path: {s}\n", .{absolute_path});
}
$ zig run getfullpath.zig
cwd_path: /home/ryan/Programming/zig/tmp
absolute_path: /home/ryan/Programming/baz/file.txt
Note: Order matters for std.fs.path.resolve
, so:
std.fs.path.resolve(allocator, &.{ "../../baz/file.txt", "/my/root/dir" });
would always return /my/root/dir
, while:
std.fs.path.resolve(allocator, &.{ "/my/root/dir", "../../baz/file.txt" });
would return /my/baz/file.txt
.
1 Like
I had to do this same thing with a small terminal emulator I was tinkering around with. I don’t know of any other way to currently do this besides composition like @squeek502 mentioned.
1 Like
It’s generally a code smell to convert a relative path into an absolute path. Why do you need to do it?
2 Likes
Thanks. std.fs.path.resolve()
seems to do what I want, despite what the docs say (or even more likely, I misunderstood the docs).
It’s a command line tool that takes an absolute path to a file as an input, then that file contains relative paths (relative to the location of the input file) to other files. I’m trying to combine the absolute path of the directory the input file is in (using std.fs.path.dirname()
) with the relative paths inside the file to generate absolute paths that I can send to std.fs.openFileAbsolute()
.
Something like this, in other words (pseudocode):
var absolutePathToInputFileDir = std.fs.path.dirname(pathToInputFile);
var relativePathToAnotherFile = readInputFileAndGetPathToDependentFile(pathToInputFile);
var absolutePathToDependentFile = std.fs.path.resolve(.{ absolutePathToInputFileDir, relativePathToAnotherFile});
var fileToRead = std.fs.openFileAbsolute(absolutePathToDependentFile);
The trick was to figure out std.fs.path.resolve()
does what I need.
1 Like
Note that Dir.openFile
handles relative or absolute paths, and that openFileAbsolute
is purely a wrapper around std.fs.cwd().openFile
. Personally I think all the Absolute
suffixed functions are redundant at best and should be removed (see this proposal).
My suggestion would be to do this instead:
const input_dir_path = std.fs.path.dirname(path_to_input_file) orelse ".";
var input_dir = try std.fs.cwd().openDir(input_dir_path, .{});
defer input_dir.close();
// could also pass the input_dir and the basename of path_to_input_file to this function instead
var another_file_path = try readInputFileAndGetPathToDependentFile(path_to_input_file);
var file_to_read = try input_dir.openFile(another_file_path, .{});
defer file_to_read.close();
This will work when path_to_input_file
is relative or absolute, and when another_file_path
is relative or absolute. See the linked proposal for some more reasons why this is preferred over the Absolute
functions.
3 Likes
+1 to what Andrew says, “don’t use absolute paths” is also a rule I internalized when working on Cargo.
What you usually want to do is to treat paths in mostly opaque ways, and construct new paths by concatenation of existing paths.
For “file with other paths” the standard pattern is this:
- If you read the contents of the file, you know it’s path.
- Typically, it is either a path passed by the user as an argument (which might be absolute or relative), or a path like
../../../my-program.config
constructed by the program itself during some search operation
- Either way, the invariant here is that’s, if you do
std.fs.cwd().readFile(path)
, you get the text of the file. The exact content of path
is mostly irrelevant, it’s just some opaque string you can pass back to the OS to get the contents of the file back.
- Now that you’ve read the file, you can concatenate paths from the contents of the file to the path of the file itself. This requires a tiny bit of path inspection (to get a dirname), but this never touches the beginning of path, and is agnostic to absolute/relative distinction.
This part is unnecessary if you use openDir
on the dirname
of the initial path (see my suggested code above). This provides a bit of protection against TOCTOU errors that could arise from the path concatenation strategy (see the linked proposal in my comment above).
1 Like