pwd Command in Linux: Complete Guide to Print Working Directory and Parameters

pwd command in Linux and it perimeters

pwd command in Linux and it perimeters

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

OptionLong FormDescription
-L--logicalPrint the logical path, including any symlink components as you cd‘d through them (this is the default behavior)
-P--physicalPrint the physical path, resolving all symlinks to their real, underlying targets
--versionPrint version information (external binary only; the shell builtin doesn’t support this)
--helpDisplay 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:

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/ConceptPurpose
pwdPrint the current working directory (logical or physical)
$PWDShell-maintained environment variable tracking the current directory, updated on cd
cdChanges the current working directory; interacts directly with how pwd -L will report afterward
realpathResolves any given path (not just the current directory) to its canonical, symlink-free absolute form
readlink -fSimilar 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

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

Exit mobile version