Skip to content
Shell & TerminalDeep Dive Published Updated 7 min readViews unavailable

pwd in Shell Scripts: Logical Paths, Physical Paths, and PWD Trust

Choose logical or physical pwd output intentionally, understand symbolic-link components, and avoid treating a path string as object identity.

pwd prints a pathname for the process’s current working directory. The word “the” can be misleading: a directory can be reached through multiple pathnames, and symbolic links allow a logical path to differ from a physical path. POSIX pwd -L may use a valid PWD environment variable to preserve the logical route used to reach the directory; pwd -P prints a physical pathname without symbolic-link components. Neither output is a permanent identifier for a directory object.

This distinction matters in build scripts, prompts, deployment tools, diagnostics, and code that constructs relative paths. If a shell enters /workspace/current where current is a symlink to a release directory, logical pwd can preserve /workspace/current; physical pwd can return the resolved release path. The first is often nicer for stable user-facing output. The second can be useful for path inspection. A script should select intentionally rather than assume the two must match.

Logical and physical modes

The logical mode depends on PWD being an absolute pathname of the current directory without . or .. components and otherwise falls back to physical behavior under the POSIX contract. Since PWD is an environment variable, it should not be treated as an independently authenticated source. Conforming shells maintain it as they change directories, but arbitrary programs can pass a modified environment to a child. When the answer is security-sensitive, derive identity from an opened directory descriptor rather than trusting a textual path.

Physical mode resolves symbolic-link components as part of path reporting. It can make logs reflect the storage location rather than a user-facing alias. That may be useful for canonicalization, but canonical paths can still change if the directory is renamed, mount topology changes, or the filesystem is remounted. Physical resolution also does not guarantee that the path is safe to open later; another actor can alter directory entries after pwd returns.

The shell may implement pwd as a builtin, while /bin/pwd or another executable provides external utility behavior. Shell builtins know the shell’s logical navigation state directly; an external command observes its own process environment and filesystem. Querying a command name from an interactive session may hit a function or alias. Test the exact invocation used in automation, especially if the script depends on -L or -P semantics.

Build paths without losing argument boundaries

Command substitution removes trailing newline characters from the command’s output. That is normally harmless for pwd, whose pathname output ends in a newline, but it is a reminder that command substitution is text capture, not an opaque path object. Quote the result when using it:

logical_root=$(pwd -L) || exit
physical_root=$(pwd -P) || exit
printf 'logical=%s\nphysical=%s\n' "$logical_root" "$physical_root"

Quoting preserves spaces and wildcard characters in later expansions. A path cannot contain NUL, but it can contain newline; command substitution cannot faithfully preserve a trailing newline embedded in the pathname because the shell strips trailing newlines from output. If the workflow accepts arbitrary directory names and needs byte-exact handling, use a language with filesystem APIs rather than serializing paths through command substitution.

The current directory itself is process state, not just a PWD string. cd changes the directory reference used for relative path resolution. A shell variable can become stale if an external process renames the directory or if the shell’s state is manipulated unexpectedly. Avoid storing pwd output for long periods as though it were a lock or stable object ID. Recheck before a sensitive operation, or use file descriptors in programs that need race-resistant traversal.

Logical and physical output can reveal different operational facts in symlink-heavy deployments. A container may expose a path through a bind mount; a chroot-like environment may hide the host path; network filesystems may return different canonical names; and automounters can change what a path resolves to over time. pwd -P is not a universal “real path” service across mount namespaces. It reports according to the process view and utility semantics.

Do not use a string-prefix check on pwd output to prove that a target remains inside a trusted directory. Prefixes can confuse /safe/root with /safe/rooted, and symlinks can redirect traversal. realpath has its own resolution and missing-component options, but even a canonical path check can race with subsequent opens. Security-sensitive containment should be enforced by descriptor-relative operations and kernel resolution constraints where available.

Choose a display and automation policy

For logs and interactive prompts, logical paths often preserve the operator’s chosen alias. For a diagnostic that compares storage locations, physical paths may be more informative. For reproducible builds, set and record the working directory explicitly instead of assuming the caller’s. In scripts, avoid using a pwd string to locate a script file; the process can be launched from anywhere, and the script can be sourced rather than executed. Use a documented script-source stack mechanism for the shell in question.

Test both modes from a real directory, a symlinked directory, a renamed directory, a mounted path, and a path containing whitespace. Test the builtin and the actual external binary separately if both are relevant. The critical rule is simple: pwd reports a path representation of the current directory under a selected policy. It does not establish provenance, freeze filesystem topology, or confer authority over whatever a later path lookup finds.

Do not confuse the working directory with a script directory

The current working directory belongs to the process and is inherited by child processes. It is not necessarily the directory containing the currently executing script. A caller can run /opt/tools/check.sh while standing in /tmp, invoke a relative symlink to the script, or source it into the current shell. In every case, pwd describes the caller’s current directory, not a universal script location. If an application needs the script’s own location, use the shell’s documented source-stack variable or pass an explicit installation root; account for symlinks and sourcing modes.

Changing directory inside a subshell does not change the parent shell’s working directory, while a builtin cd in the current shell does. A script that runs cd should handle failure immediately and decide whether later relative paths are anchored to the new directory. A common reliability pattern captures a validated project root once, changes to it explicitly, and then uses absolute or root-relative paths. That reduces dependence on the operator’s launch location but still requires a deliberate symlink policy.

PWD is a convenience, not proof

Shells maintain PWD to preserve logical navigation, but any process can pass environment values to a child. A utility may validate the variable against the actual current directory before trusting it, and an invalid value can trigger physical fallback. Do not use a string from PWD as an audit record of how the process arrived at its directory, because it can be unset, normalized, or rewritten by a wrapper.

If a program needs to prove that a directory is beneath a trusted root, comparing pwd strings is insufficient. Normalize boundaries, account for symlink traversal, and preferably use directory file descriptors with constrained resolution. Likewise, a pwd -P output captured at startup is only a snapshot: another process with sufficient rights can rename ancestors, and mount namespaces can present different path views. Use path strings for diagnostics and user interfaces, and use filesystem identity mechanisms for security decisions.

Make diagnostics unambiguous

Logs can report both logical and physical paths to explain aliases, but label them clearly and quote them safely. A path may contain spaces, control characters, or newline, so printing it as an unescaped field can create misleading log records. Use a structured logging encoder or a byte-safe quoting function when directory names are not controlled. Avoid using pwd output as a delimiter-separated protocol unless the delimiter is excluded by policy.

Tests should start a process from a physical path, a logical symlink path, a directory containing whitespace, and a path that is renamed while the process remains inside it. Compare builtin and external pwd under their supported options. These cases reveal whether a script depends on shell-maintained logical state or on physical path resolution and make the intended contract visible to future maintainers.

Related:

Sources:

Comments