resolve-path — Canonicalize a path — realpath equivalent across all 5 shells
Equivalents in every shell
realpath -e file.txt`realpath` is GNU coreutils (Linux default). `-e` requires every component to exist; `-m` permits non-existent (good for "what would the absolute path be"); `-s` does NOT resolve symlinks (lexical only). On macOS, `realpath` arrived in 12+; older Macs use `greadlink -f` from coreutils-via-brew.
echo ${file:A}zsh has the `:A` modifier on parameter expansion — `${file:A}` resolves symlinks AND canonicalises (closest to GNU `realpath -e`). `${file:a}` is lexical-only (closer to `realpath -s`). No external command needed; faster than `realpath` for one-off use. macOS bash users without coreutils often shell out to `pwd -P` after `cd`.
path resolve file.txtFish-native `path resolve file.txt` (since fish 3.5) — built-in, no fork, returns absolute path with symlinks resolved. `path` is fish's general-purpose path manipulation builtin (also `path basename`, `path dirname`, `path extension`). External `realpath` also works on systems that ship it.
Resolve-Path file.txtPowerShell-native cmdlet. Returns a `[PathInfo]` object — `.Path` is the full string; `.Provider` is the PS provider. THROWS a terminating error if the path does not exist (unlike `realpath -m`). For non-existent paths use `[System.IO.Path]::GetFullPath((Join-Path (Get-Location) "missing.txt"))` (lexical, never throws).
for %i in (file.txt) do @echo %~fiNo native `realpath`. The closest idiom is `%~dpnx<arg>` which expands argument N to its full Drive-Path-Name-eXtension form — but only inside a batch file, not interactive. `for %i in (file.txt) do @echo %~fi` is the interactive workaround (`%~fi` = full path). Neither follows symlinks; for that, PowerShell or WSL.
Worked examples
Get the absolute path of a relative file
realpath ./config.jsonfile=./config.json; echo ${file:A}path resolve ./config.jsonResolve-Path ./config.jsonfor %i in (config.json) do @echo %~fiResolve a symlink to its real target
realpath -e /usr/bin/python3file=/usr/bin/python3; echo ${file:A}(Get-Item /usr/bin/python3).TargetCompute an absolute path even when the file does not exist
realpath -m ./not-yet-created.txt[System.IO.Path]::GetFullPath((Join-Path (Get-Location) "not-yet-created.txt"))Gotchas
- `Resolve-Path` THROWS a terminating error (`Cannot find path because it does not exist`) if the path does not exist — surprising to bash users used to `realpath -m`. The two clean workarounds: `Resolve-Path missing.txt -ErrorAction SilentlyContinue` (returns `$null`), or `[System.IO.Path]::GetFullPath((Join-Path $PWD 'missing.txt'))` (lexical, never throws). Pick based on whether you need symlink-resolution.
- `Resolve-Path` returns ALL paths matching its wildcards — `Resolve-Path *.log` returns multiple `[PathInfo]` objects. Iterate or pull `.Path`. This trips bash users who expect a single string back like `realpath`.
- macOS shipped `realpath` in 12+ (Monterey); on older macOS the standard binary did NOT exist. `readlink -f` (GNU style) ALSO does not exist on BSD/macOS — BSD `readlink` is one-hop only. The portable macOS-and-Linux idiom is `cd "$(dirname "$f")" && pwd -P` followed by `basename`, OR install coreutils-via-brew (`greadlink -f`).
- The zsh `${file:A}` modifier resolves AS-IF the file is in the current directory — this means relative `./foo` resolves correctly, but a bare `foo` (no leading `./`) is treated as a name, not a path, and may NOT resolve as expected. Always pass `./foo` or `$PWD/foo` to `:A` for a leading path; or use the more explicit `${(:-./foo):A}` form.
- `Resolve-Path` on a UNC path (`\\server\share\file`) requires the share to be REACHABLE — it actively touches the network. For pure-string canonicalisation (no I/O), `[System.IO.Path]::GetFullPath` is the right tool; for UNC scripting that should not block on offline shares, wrap in `Test-Path -ErrorAction SilentlyContinue` first.