pwd — print working directory
Overview
pwd prints the current working directory — but “the” directory has two
representations, and which one you get depends on which pwd you ran.
The shell tracks a logical path in $PWD (what you typed, with
symlinks intact), while the kernel’s getcwd(3) returns the physical
path (every symlink resolved). The shell builtin defaults to logical
(-L); the coreutils binary defaults to physical (-P). That asymmetry
is the single most quoted fact about this command.
The binary ships in the Debian coreutils package at /usr/bin/pwd
(/bin/pwd is a merged-/usr symlink to it). Almost every interactive
invocation actually runs the builtin — type -a pwd shows shell builtin before /usr/bin/pwd — which is why help output, defaults, and
error messages differ between contexts. The command is POSIX-standardized
and dates to the original Unix; it is often confused with $PWD (a
variable the shell maintains and can be wrong), pwd -P in scripts
(the fix for symlink surprises), and $OLDPWD (the shell’s previous-dir
convenience).
| Field | Value |
|---|---|
| Package | coreutils (Debian bookworm) |
| Man section | 1 |
| Path | /usr/bin/pwd |
| First appeared | original AT&T Unix (Version 1 era) |
| Standards | POSIX 2018 utility |
Synopsis
pwd [OPTION]...
Main forms:
pwd # builtin default: logical ($PWD) if it is consistent
pwd -P # physical: resolve every symlink (always getcwd)
pwd -L # logical: print $PWD even if it contains symlinks
/usr/bin/pwd # the binary, which assumes -P when no option is given
How It Works
Two sources of truth cooperate:
shell builtin pwd (default -L) /usr/bin/pwd (default -P)
┌────────────────────────────┐ ┌──────────────────────────┐
│ print $PWD if it names the │ │ getcwd(3): walk inodes │
│ current dir, else fall │ │ back up via ".." │
│ back to getcwd() │ │ → fully physical path │
└────────────────────────────┘ └──────────────────────────┘
trusting shell bookkeeping trusting only the kernel
The kernel does not remember names — only inodes. getcwd(3) therefore
reconstructs the path by walking .. to the root, resolving every
symlink by construction. The shell, by contrast, does lexical
bookkeeping: cd /var && cd tmp (through a symlinked tmp) sets
$PWD=/var/tmp — logical, symlink intact, and cheaper than asking the
kernel.
The coreutils help states its own default explicitly: “If no option is
specified, -P is assumed”, while bash’s builtin help says “By default,
pwd behaves as if -L were specified”. Same command name, opposite
defaults:
$ pwd # builtin, -L: the logical path
/tmp/g2
$ /bin/pwd # binary, -P: physical resolution
/tmp/g2
-L is not blind: the builtin prints $PWD only when it still names
the current directory. Hand it a stale or lying $PWD and it silently
falls back to getcwd:
$ PWD=/tmp/g2/.. /bin/pwd -L # $PWD does not name the cwd
/tmp/g2 # -> physical fallback, no error
(The example is deliberate: /tmp/g2/.. refers to /tmp, not the cwd,
so the check fails and getcwd wins.)
With symlinked directories the two modes genuinely diverge — the classic scenario is a link into a deep tree:
# /opt/rel -> /var/lib/app-v2 (symlink to a directory)
$ cd /opt/rel/config
$ pwd # builtin -L: keeps the path you typed
/opt/rel/config
$ pwd -P # resolves every link
/var/lib/app-v2/config
Scripts that compute locations from $PWD or plain pwd can break when
any component is a symlink that later gets retargeted (version flips,
atomic deploys); pwd -P (or the binary) pins them to reality.
Options That Matter
| Option | Effect |
|---|---|
-L, --logical | Print $PWD even if it contains symlinks (builtin default; validation applies) |
-P, --physical | Resolve all symlinks — always getcwd(3) (binary default) |
--help / --version | Informational |
Both modes are POSIX-specified, including the opposite defaults between
shells’ builtins and the standalone utility. POSIX also permits -L
output to differ from -P only when the logical path is reachable;
if $PWD is unset or inconsistent, both modes converge on getcwd.
Usage Patterns
# Script locates itself through symlinks (deploy dirs are symlinks)
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
# Record the physical directory in a log for post-mortems
echo "ran in $(pwd -P)" >> run.log
# Show where a symlinked path really lives
cd /opt/rel && pwd -P
# Confirm shell logical bookkeeping matches the kernel view
[ "$(pwd)" = "$(pwd -P)" ] || echo "inside a symlink chain"
# Use $OLDPWD for a two-step compare
cd /etc && diff <(ls) <(cd "$OLDPWD" && ls)
# Physical cwd in a prompt without symlink clutter
PS1='\w \$ ' # bash already uses $PWD; for physical: $(pwd -P)
# Capture cwd before a cd-heavy function runs
BASE=$(pwd -P); cd /tmp/work || exit; ...; cd "$BASE"
# Guard against deleted-cwd weirdness in long-lived shells
cd "$(pwd -P)" 2>/dev/null || echo "cwd was removed; re-home me"
Nuances and Gotchas
- Opposite defaults between builtin and binary.
pwdin bash is logical;/usr/bin/pwdis physical. A one-liner that “works in my shell” can behave differently insh -cunder a shell without a pwd builtin (dash has one, but tiny shells may not). $PWDis just an environment variable. Any parent process can set it to garbage; the builtin validates (stat-compare) and falls back, but/usr/bin/pwd -Lalso validates before trusting. Never use$PWDin security-relevant logic without validation.getcwdfails if the cwd was deleted (pwd: cannot get current directoryor an error per implementation) — a real failure mode for long-lived shells and daemons whose working directories were removed out from under them.pwd -Lprints the bookkeeping path, which is lexical: aftercd ..through a symlinked directory you may land somewhere your intuition disagrees with —cd /opt/rel/config && cd ..puts you in/opt/rellogically but/var/lib/app-v2physically, and both are “correct”.- Name length limits.
getcwd(3)historically had a buffer-size failure mode for deep trees (getcwd: cannot access parent directories); the binary handles this by allocating dynamically, but shells’ builtin-Lpath can still print more than fits in legacy buffers. PWDandpwddisagree across mounts: bind mounts and overlayfs (containers) can make the physical path differ from expectations in ways no option resolves — the kernel reports what it sees.pwdoutput is newline-terminated; command substitution strips it, but scripts doing$(pwd)insideprintf '%s'styling must remember$(pwd)(not$(pwd; echo)oddities) — the usual quoting rules apply.
Exit Status
| Code | Meaning |
|---|---|
0 | Path printed |
1 | getcwd(3) failed: cwd deleted, permission trouble walking to root, or path exceeds limits |
2 | Invalid option (bash builtin convention for bad usage) |
Related Commands
./overview.md— GNU Coreutils collection hub../readlink.md—readlink -fis the name-resolving sibling ofpwd -P../pathchk.md— validate the long paths pwd can produce.../../shell/bash.md—$PWD,$OLDPWD, and cd’s logical bookkeeping.
Interview Questions
Q: What is the difference between pwd’s -L and -P modes?
-L prints the shell’s logical path from $PWD — the route you typed,
with symlinks unexpanded — while -P asks the kernel (getcwd(3)) for
the physical path with every symlink resolved. In a directory reached
through a symlink, pwd shows /opt/rel/config and pwd -P shows
/var/lib/app-v2/config. Both are truthful; they answer “what did I
say” versus “where is this actually”.
Q: Why do the bash builtin and /usr/bin/pwd disagree on the default?
POSIX specifies both: the standalone utility defaults to -P (its help:
“If no option is specified, -P is assumed”), while shells’ builtins
default to -L because the shell maintains $PWD as part of cd’s
logical bookkeeping. Since the builtin shadows the binary in interactive
use, people learn “pwd is logical” and are surprised by the binary — the
interviewer is testing whether you know which program actually ran.
Q: A script computes its own directory with pwd and breaks when deployed via a symlinked release dir. Fix it.
Plain pwd (builtin, logical) reports the symlinked path, and when the
deploy flips the symlink to a new release, relative logic anchored to the
old logical path can misfire. Fix by resolving physically:
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) — or
readlink -f. The pattern is standard precisely because release
directories, current symlinks, and bind mounts make logical paths
unstable.
Q: When does pwd fail even though the process is running?
When the working directory has been deleted (rmdir’d or its mount
removed): getcwd(3) cannot reconstruct a path to a nonexistent inode,
so pwd errors and exits nonzero. Long-lived shells and services in
ephemeral directories hit this; the fix is to re-home (cd to a valid
directory) or restart. It is a classic post-mortem finding for cron jobs
whose spool directories were cleaned.
Q: Is $PWD trustworthy?
Only after validation. It is an ordinary environment variable inherited
from the parent process, so anything can set it. The builtin’s -L mode
(and /usr/bin/pwd -L) stat-compares $PWD against the actual cwd and
silently falls back to getcwd on mismatch — the observed behavior —
but scripts should treat $PWD as a hint, never as evidence, and use
pwd -P (or the binary) where the physical location matters.