Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

Linux Show Current Path: pwd, $PWD, and Why They Sometimes Disagree

Sean

Platform Writer

Aug 07, 2026
8 min read

Run pwd to print the current working directory, or echo $PWD to read it from the shell variable — and if those two ever disagree, a symlink is involved.

Linux Show Current Path: pwd, $PWD, and Why They Sometimes Disagree

For most of the time you use it, pwd is the least interesting command on the system. The part worth ten minutes is what happens when the path you walked in through is not the path the kernel thinks you are standing on, because that is where scripts start writing files somewhere unexpected.

Table of contents

The basics

pwd
# /home/deploy/projects/api

echo "$PWD"
# /home/deploy/projects/api

echo "$OLDPWD"      # where you were before the last cd
# /home/deploy

pwd is both a shell builtin and a binary at /bin/pwd. The builtin is what runs when you type it interactively; the binary is what runs when something invokes it via env or find -exec. They differ in one specific way, covered below.

$PWD is maintained by the shell and updated on every cd. Reading it is faster than calling pwd because it requires no process and no system call, which matters inside a loop and nowhere else.

cd - jumps back to $OLDPWD, which is one of the genuinely useful shortcuts. Toggling between two directories does not need a stack or a bookmark.

This is where the command gets interesting. Suppose /srv/current is a symlink to /srv/releases/2026-08-07 — the standard blue-green deploy layout.

cd /srv/current

pwd            # /srv/current              (logical, the default)
pwd -P         # /srv/releases/2026-08-07  (physical, symlinks resolved)
  • -L (logical) keeps the path you navigated through, symlinks intact. This is the default.
  • -P (physical) resolves every symlink and gives you the real location on disk.

Both are correct answers to different questions. The logical path is where you think you are. The physical path is where the filesystem says you are.

The consequence that catches people: cd .. follows the logical path.

cd /srv/current
cd ..
pwd            # /srv        -- the parent of the symlink

cd /srv/current
cd -P ..
pwd            # /srv/releases -- the parent of the real directory

A deploy script that does cd /srv/current && cd ../shared lands somewhere completely different depending on which mode the shell is in, and the failure is a file written to the wrong directory rather than an error.

The builtin and the binary disagree

Here is the wrinkle. /bin/pwd has no access to the shell’s variables, so it always reports the physical path regardless of flags the shell would have honoured.

cd /srv/current

pwd           # /srv/current              (builtin, logical)
/bin/pwd      # /srv/releases/2026-08-07  (binary, always physical)
command pwd   # /srv/current              (still the builtin)

So a script that calls pwd behaves one way, and the same script calling $(/bin/pwd) behaves another. If you care which you get, be explicit — pwd -P for the real location, pwd -L for the navigated one — rather than relying on which binary got resolved.

You can also make the whole shell physical by default:

set -P          # cd and pwd resolve symlinks from here on
set +P          # back to logical

Useful at the top of a deploy script where symlinked release directories are in play and you want no ambiguity about where anything lands.

Finding a script’s own directory

This is the reason most people end up reading about pwd. A script needs to reference a file next to it, and the working directory is wherever the caller happened to be.

#!/usr/bin/env bash
set -euo pipefail

# the directory containing this script, symlinks resolved
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

source "$SCRIPT_DIR/lib/common.sh"
config="$SCRIPT_DIR/../config/settings.yml"

Reading that from the inside out: ${BASH_SOURCE[0]} is the script’s own path, dirname strips the filename, cd into it, and pwd -P prints the absolute resolved location. The subshell means the cd does not affect the rest of the script.

Use ${BASH_SOURCE[0]} rather than $0. They are the same when a script is executed, and they differ when it is sourced — $0 gives you the sourcing shell’s name, so a sourced library resolves to the wrong directory.

The -- in dirname -- and cd -- stops a path beginning with a hyphen being parsed as an option. It looks like paranoia until a directory named -p exists.

In POSIX sh there is no BASH_SOURCE, so $0 is what you have — and sourced scripts genuinely cannot determine their own path portably.

When the directory is gone

A shell can sit in a directory that no longer exists, because the process holds a reference the filesystem has already unlinked.

cd /tmp/scratch
rm -rf /tmp/scratch      # from another terminal

pwd
# /tmp/scratch          -- $PWD still says so

pwd -P
# pwd: error retrieving current directory: getcwd: cannot access parent directories

ls
# ls: cannot open directory '.': No such file or directory

The logical pwd answers from the shell’s stored variable and looks fine. The physical one asks the kernel and fails. Every relative path operation fails from here, usually with errors that do not mention the deleted directory.

cd . will not save you — there is no . any more. The fix is an absolute path: cd /tmp or cd ~.

Worth recognising the symptom. A terminal where every command suddenly cannot find anything, in a session that was working a minute ago, is usually this rather than something dramatic.

Where working directories matter in production

Interactively, none of this is consequential. In automation it decides whether the right file gets read.

A cron job starts in the user’s home directory. A systemd unit starts in / unless WorkingDirectory= says otherwise. A container starts wherever WORKDIR was set. A CI runner starts in a checkout path that includes a build number. None of these are the directory you were in when you tested the script.

The rule that avoids the whole category: never depend on the working directory in a script. Derive an absolute base from the script’s own location with the SCRIPT_DIR pattern above, or take paths as arguments, or read them from environment variables. Relative paths are fine once they are anchored to something you computed rather than something you inherited.

This is also why deployment reproducibility is worth having rather than assuming. A build that runs on the platform from the repository root, every time, with the same environment, removes the class of bug where something works on one machine and not another. On RunxBuild the build runs the same way per deploy and the log shows what happened — which turns “it worked locally” from a mystery into a diff you can read.

How this fits the rest of the stack

Predictable builds cost nothing extra; the runtime, database, storage, and bandwidth they produce do. The RunxBuild hosting calculator puts those line items on one page so the monthly total is something you modelled rather than discovered.

Useful related references:

FAQ

How do I show the current path in Linux?

Run pwd to print the working directory, or echo $PWD to read the shell variable the shell updates on every cd. $OLDPWD holds the previous directory, and cd - jumps back to it.

What is the difference between pwd -L and pwd -P?

-L is logical and keeps the path you navigated through with symlinks intact, which is the default. -P is physical and resolves every symlink to the real filesystem location. They differ only when a symlink is in the path.

Why does pwd give a different answer from /bin/pwd?

The shell builtin knows the logical path you navigated through; the /bin/pwd binary has no access to shell state and always reports the physical resolved path. Be explicit with pwd -L or pwd -P rather than relying on which one gets resolved.

How do I get the directory a bash script is in?

SCRIPT_DIR=”$(cd — ”$(dirname — ”${BASH_SOURCE[0]}”)” && pwd -P)”. Use BASH_SOURCE[0] rather than $0, since they differ when a script is sourced and $0 then gives the sourcing shell’s name instead of the script path.

Why does every command fail with cannot access parent directories?

The directory your shell is in was deleted while you were in it. The logical pwd still answers from the stored variable, but the kernel cannot resolve the path, so every relative operation fails. cd . will not help — use an absolute path such as cd ~ to get out.

#linux show current path#pwd command#working directory#linux symlinks#shell scripting