readlink — Print the target of a symbolic link, or the canonical path across all 5 shells
Equivalents in every shell
readlink -f ./symlinkExternal (GNU coreutils on Linux). `readlink path` prints the IMMEDIATE target of a symlink (one hop). `readlink -f path` recursively resolves through all symlink hops AND `..` segments — the canonical absolute path. `-e` requires every component to exist; `-m` allows the final to be missing.
readlink -f ./symlinkSame external. Zsh's `${name:A}` parameter expansion does the same in pure shell with no subprocess: `p=./symlink; print -r -- ${p:A}`. Use `:P` (zsh 5.3+) for safer "physical path" resolution that follows symlinks across all parents.
readlink -f ./symlinkExternal. Fish 3.6+ ships the builtin `path resolve`: `path resolve ./symlink` canonicalises (resolves symlinks, collapses `..`) without spawning `readlink`. `path readlink` mirrors the one-hop form for completeness.
(Get-Item ./symlink).TargetReturns the IMMEDIATE target string (one hop). `(Get-Item ./symlink).LinkType` reports `SymbolicLink` / `Junction` / `HardLink`. For canonical absolute paths use `Resolve-Path` (errors on missing) or `[System.IO.Path]::GetFullPath($p)` (lexical canonicalisation; allows missing).
dir /al .\symlinkNo native `readlink`. `dir /al` (lowercase L) lists reparse points (junctions and symlinks) with their targets in the output. `fsutil reparsepoint query path` exposes the same info programmatically. Canonicalisation of relative paths uses `for %i in (path) do @echo %~fi`.
Worked examples
Print the IMMEDIATE target of a symlink (one hop)
readlink ./bin/pythonreadlink ./bin/pythonpath readlink ./bin/python(Get-Item ./bin/python).Targetdir /al .\bin\pythonResolve a symlink chain to the final canonical absolute path
readlink -f /usr/bin/python3p=/usr/bin/python3; print -r -- ${p:A}path resolve /usr/bin/python3(Resolve-Path /usr/bin/python3).PathGet the directory containing the running script
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")SCRIPT_DIR=${0:A:h}$SCRIPT_DIR = Split-Path -Parent $PSCommandPathGotchas
- macOS BSD `readlink` lacks `-f` on systems older than macOS 13.x — the flag is GNU-only and does NOT work on legacy installs. Use `brew install coreutils` and call `greadlink -f`, or fall back to Python: `python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$path"` for portable scripts.
- Bare `readlink path` (no `-f`) errors with exit 1 and empty output when `path` is NOT a symlink. Wrap in a check (`[ -L "$p" ] && readlink "$p"`) or use `readlink -f` which silently returns the canonical path of a regular file. The bare form is a frequent cause of empty output in scripts.
- `readlink` vs `realpath`: `readlink -f` and `realpath` produce the same canonical path on Linux for existing files, but `realpath` is the cleaner, more portable name in newer GNU releases. See the /cmd/realpath page for the broader picture — they overlap heavily but query intent differs.
- PowerShell `Resolve-Path` THROWS if the path does not exist. Use `[System.IO.Path]::GetFullPath($p)` (works on missing paths) when you only need lexical canonicalisation. `(Get-Item ./link).Target` only resolves ONE hop — pwsh has no built-in recursive symlink resolver short of a manual loop.
- Cmd `dir /al` is the only built-in way to see a symlink's target, and it prints in the locale-formatted directory layout (date, size, target) — not a clean target-only line. Scripts that need just the target should pipe through `for /f` or shell out to PowerShell.