pwd is about as small as commands get — it prints your current directory and does essentially nothing else — and for a long time I never thought there was anything more to learn about it. Then I ran into a situation where pwd and pwd -P gave genuinely different answers inside a symlinked directory, and realized this tiny command actually encodes a real distinction about how Linux paths work: the difference between where you logically are and where you physically are on disk. This guide covers that distinction along with everything else worth knowing about pwd.
What Is the pwd Command?
pwd (print working directory) outputs the absolute path of the current working directory — the directory your shell session is currently “in,” which determines how relative paths are resolved for every other command you run. It exists both as a standalone executable (/usr/bin/pwd, part of GNU coreutils) and, critically, as a built-in command implemented directly inside bash and other shells.
Basic Syntax
pwd [OPTION]...
A Basic Example
$ cd /home/claude
$ pwd
/home/claude
Simple enough on the surface. The interesting behavior shows up once symbolic links enter the picture.
Full Parameter Reference
| Option | Long Form | Description |
|---|---|---|
-L | --logical | Print the logical path, including any symlink components as you cd‘d through them (this is the default behavior) |
-P | --physical | Print the physical path, resolving all symlinks to their real, underlying targets |
--version | Print version information (external binary only; the shell builtin doesn’t support this) | |
--help | Display usage information (external binary only) |
The Logical vs Physical Distinction
This is the entire reason pwd has more than one option worth knowing. Consider a symlink pointing from one directory to another:
$ mkdir -p /tmp/linktest/real
$ ln -s /tmp/linktest/real /tmp/linktest/link
$ cd /tmp/linktest/link
$ pwd
/tmp/linktest/link
$ pwd -P
/tmp/linktest/real
Both commands are run from the exact same location, but they report different answers:
pwd(equivalent topwd -L, the default) reports the logical path — the path you actually typed and navigated through, symlink and all, exactly as your shell tracked it during thecd.pwd -Preports the physical path — the real, canonical location on disk after fully resolving every symlink component.
Both answers are “correct,” they’re just answering different questions: “what path did I navigate through” versus “where does this actually live on the filesystem.”
How pwd Works Internally
There are two distinct implementations behind pwd, and understanding both explains the logical/physical distinction directly:
The shell builtin (what actually runs by default when you type pwd in bash, unless you use the full path) maintains an internal variable, $PWD, that the shell updates every time you successfully cd. Critically, when you cd into a path containing a symlink, the shell — by default — keeps track of the path as you typed it, symlink components included, rather than immediately resolving it to the canonical target. This is exactly why pwd (the builtin, logical mode) can report a path containing a symlink name that doesn’t correspond to a “real” directory entry at that exact location.
The external binary (/usr/bin/pwd), when run directly or when the builtin’s -P mode is used, instead determines the current directory using the getcwd() system call, which asks the kernel directly for the canonical, symlink-resolved absolute path of the process’s current working directory — the kernel doesn’t track “how you got here” the way the shell does, it only knows the real, physical location.
This is also why $PWD (the environment variable) and the actual result of getcwd() can diverge: $PWD is purely a shell-maintained bookkeeping variable, updated on every cd, pushd, or popd, and it’s entirely possible (though unusual) for it to become stale or incorrect if something external moves or removes the underlying directory without the shell’s involvement.
Confirming Which pwd Is Running
Because pwd exists both as a builtin and an external binary, it’s worth checking which one your shell will actually invoke when you type pwd:
$ type pwd
pwd is a shell builtin
In bash, the builtin takes precedence unless you explicitly call the full path (/usr/bin/pwd) or your shell is configured to prefer external commands (POSIXLY_CORRECT and similar settings can affect this in some configurations). The two implementations behave the same way in physical mode, but only the builtin has direct, real-time access to the shell’s own symlink-tracking state for logical mode — this is why calling /usr/bin/pwd directly effectively always behaves like -P in practice for symlink-traversed paths, since the external binary has no visibility into how you navigated there, only where you physically are right now.
Real-World Use Cases
1. Confirming Your Location in a Script Before a Destructive Operation
#!/bin/bash
set -euo pipefail
echo "About to clean up files in: $(pwd)"
read -rp "Continue? (y/n) " confirm
[[ "$confirm" == "y" ]] || exit 1
rm -rf ./build/*
2. Building Absolute Paths From Relative Ones in Scripts
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "This script lives in: $SCRIPT_DIR"
This pattern — cd into a directory (often computed from dirname of the script’s own path) and then immediately pwd to capture the fully-resolved absolute path — is extremely common at the top of shell scripts that need to reliably reference files relative to their own location, regardless of what directory the script was invoked from.
3. Diagnosing Symlink-Related Confusion
$ pwd
/mnt/data/current
$ pwd -P
/mnt/data/releases/v2.4.1
If a script’s behavior seems inconsistent depending on how a directory was reached, comparing pwd and pwd -P output quickly reveals whether a symlink (like a common “current release” pointer used in deployment setups) is involved.
4. Logging the Execution Context of Automated Jobs
echo "[$(date -Iseconds)] Job started in $(pwd -P)" >> /var/log/myjob.log
Using -P here ensures the log records the real physical location, useful for debugging when the job might be invoked from different symlinked entry points but you want a canonical, unambiguous record of where it actually ran.
Shell Scripting and Automation
A robust “return to original directory” pattern using pwd, common in scripts that need to temporarily change location and then restore it:
#!/bin/bash
# process_and_return.sh - work in a target dir, then reliably return
set -euo pipefail
ORIGINAL_DIR="$(pwd)"
TARGET_DIR="/tmp/workspace"
cd "$TARGET_DIR"
echo "Working in: $(pwd)"
# ... do work ...
cd "$ORIGINAL_DIR"
echo "Returned to: $(pwd)"
For more robust nested cases, pushd/popd (bash builtins that maintain a directory stack) are often preferred over manually saving pwd‘s output, since they handle nested save/restore automatically:
pushd /tmp/workspace > /dev/null
# ... do work ...
popd > /dev/null
pwd vs Related Commands and Concepts
| Command/Concept | Purpose |
|---|---|
pwd | Print the current working directory (logical or physical) |
$PWD | Shell-maintained environment variable tracking the current directory, updated on cd |
cd | Changes the current working directory; interacts directly with how pwd -L will report afterward |
realpath | Resolves any given path (not just the current directory) to its canonical, symlink-free absolute form |
readlink -f | Similar to realpath, resolves symlinks in an arbitrary path |
dirname "$0" / ${BASH_SOURCE[0]} | Used alongside pwd to determine a script’s own location robustly |
realpath is worth knowing alongside pwd specifically because it generalizes the same “resolve symlinks” logic to any path, not just the current directory:
$ realpath /tmp/linktest/link
/tmp/linktest/real
Troubleshooting Common Issues
Problem: pwd reports a path that doesn’t seem to exist when checked from elsewhere. You’re very likely seeing the logical path through a symlink. Run pwd -P to get the real, physical location, and use that for anything that needs to match what other tools (which typically resolve symlinks by default) will report.
Problem: $PWD and the output of pwd seem to disagree. This is unusual but can happen if something external to the shell manipulated the working directory state without going through a normal cd (rare in practice, but possible in unusual scripting contexts). Running cd . will force the shell to refresh $PWD against reality.
Problem: A script behaves differently when run via a symlinked path vs the “real” path. If your script makes decisions based on pwd output, decide explicitly whether you want logical or physical behavior and use -L/-P accordingly rather than relying on the shell’s default, especially if the script might be invoked through different symlinked entry points (a common pattern in deployment directories with a “current” symlink pointing at the active release).
Problem: /usr/bin/pwd --version fails inside a script that assumes the external binary. If you’re inside bash and just type pwd --version, you’re likely hitting the builtin, which doesn’t support --version/--help the same way the external binary does. Call the full path explicitly (/usr/bin/pwd --version) if you specifically need the external implementation’s behavior.
Performance Optimization
pwd is effectively free from a performance standpoint — the builtin is a simple variable lookup (in logical mode) or, at most, a single getcwd() syscall (in physical mode). There is no meaningful performance tuning relevant to this command; it’s about as lightweight as any operation on the system.
Security Implications
pwd itself introduces no direct security risk — it doesn’t modify anything and only reports information about the current process’s directory context. The relevant security consideration is more about how scripts use its output: constructing paths by concatenating $(pwd) with user-controlled input without proper quoting or validation can introduce path-traversal or injection issues downstream, particularly if the working directory itself could be attacker-influenced (for example, in a CI job running inside a directory whose name comes from an untrusted branch name or file path). Always quote "$(pwd)" in scripts and validate any subsequently constructed paths before using them in destructive operations.
Compatibility Across Distributions
pwd as a shell builtin behaves consistently across bash on every major Linux distribution, since the behavior is defined by the shell itself rather than the distribution. The external /usr/bin/pwd binary, part of GNU coreutils, is present by default on Ubuntu, Debian, Fedora, RHEL, Arch, and openSUSE with consistent -L/-P support. POSIX shells generally guarantee both the builtin and the -L/-P flags per the POSIX specification, so this is one of the more portable commands across bash, dash, zsh, and other POSIX-compliant shells — differences are more likely at the very old end (ancient Bourne shell derivatives) than across modern systems.
Best Practices
- Use
pwd -P(orrealpath) whenever you need the true, canonical filesystem location — particularly in logging, auditing, or anywhere symlink ambiguity could cause confusion downstream. - Use the default logical
pwdwhen you want to preserve and report the path the user or script actually navigated through, symlinks included — useful for user-facing messages where the symlinked “friendly” path is more meaningful than the physical target. - Quote
"$(pwd)"in scripts to avoid word-splitting issues with directories containing spaces. - Prefer
pushd/popdover manualpwd-based save/restore when a script needs to nest multiple directory changes. - When a script needs to reliably locate its own directory, combine
dirname,BASH_SOURCE, andpwdtogether rather than relying on the invocation-time working directory.
Summary
pwd looks trivial and mostly is, but it encodes a genuinely useful distinction between the logical path you navigated (symlinks and all) and the physical, canonical path the kernel actually resolves to via getcwd(). Knowing when to reach for -L versus -P — and understanding that the shell builtin and the external binary aren’t quite the same thing — turns a command most people never think twice about into a precise tool for scripts and diagnostics where symlink ambiguity genuinely matters.
References
- GNU Coreutils Manual:
pwd— https://www.gnu.org/software/coreutils/manual/html_node/pwd-invocation.html - Bash Reference Manual: Bourne Shell Builtins (
pwd) — https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html - Linux man-pages project:
man 1 pwdandman 2 getcwd— https://man7.org/linux/man-pages/man1/pwd.1.html - POSIX Specification:
pwd— https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pwd.html
