Skip to content
shellmap

readlinkPrint the target of a symbolic link, or the canonical path across all 5 shells

Equivalents in every shell

Bashunix
readlink -f ./symlink

External (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.

Zshunix
readlink -f ./symlink

Same 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.

Fishunix
readlink -f ./symlink

External. 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.

PowerShellwindows
(Get-Item ./symlink).Target

Returns 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).

cmd.exewindows
dir /al .\symlink

No 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)

Bash
readlink ./bin/python
Zsh
readlink ./bin/python
Fish
path readlink ./bin/python
PowerShell
(Get-Item ./bin/python).Target
cmd.exe
dir /al .\bin\python

Resolve a symlink chain to the final canonical absolute path

Bash
readlink -f /usr/bin/python3
Zsh
p=/usr/bin/python3; print -r -- ${p:A}
Fish
path resolve /usr/bin/python3
PowerShell
(Resolve-Path /usr/bin/python3).Path

Get the directory containing the running script

Bash
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
Zsh
SCRIPT_DIR=${0:A:h}
PowerShell
$SCRIPT_DIR = Split-Path -Parent $PSCommandPath

Gotchas

  • 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.

WSL & PowerShell Core notes

pwsh`(Get-Item).Target` and `Resolve-Path` work identically on Windows, Linux, and macOS pwsh. Path separator (`\` vs `/`) differs by platform; normalise with `[System.IO.Path]::DirectorySeparatorChar` if comparing paths across machines. Windows-only types like NTFS junctions surface as `LinkType=Junction` only on Windows.
WSLWSL `readlink -f /mnt/c/Users/me/link` returns a Linux-style path. To convert to a Windows path, pipe through `wslpath -w`: `wslpath -w $(readlink -f /mnt/c/file)` → `C:\file`. The reverse (`wslpath -u`) translates Windows → Linux — essential for any script crossing the WSL boundary.

Common tasks using readlink

Related commands