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

Bash CDPATH: Directory Search Without Script Surprises

Understand when Bash searches CDPATH, why a successful cd can print to stdout, and how to keep scripts pinned to the intended directory.

The same command, cd build, can reach different directories in two shells even when both start in the same working directory. Bash consults CDPATH, a colon-separated search list for relative directory operands. When it finds a match through a non-empty CDPATH entry, cd also writes the selected absolute path to standard output. Those behaviors are convenient interactively and surprising in scripts that assume a relative operand means “under the current directory” or that cd is silent.

This is a Bash-focused guide to that lookup and output contract. It does not repeat the mechanics of pushd and popd; the important difference is that CDPATH changes where a relative name resolves before the shell changes directories.

Separate absolute paths from relative lookup

An absolute operand begins with /, so Bash does not search CDPATH for it. For relative operands, Bash skips the search list when the pathname begins with ./ or ../; bare names such as demo can be looked up through the colon-separated entries. A null entry means the current working directory; therefore a leading or trailing colon, or two adjacent colons, adds the current directory as one of the search locations.

Consider a developer who configures:

export CDPATH="$HOME/work:$HOME/src"

If both locations contain a directory named demo, cd demo selects the first matching entry in the configured order. It is not equivalent to cd ./demo: the latter explicitly names a path from the current directory and avoids the search list. Likewise, ../build is anchored at the current directory and bypasses CDPATH.

The chosen location is based on the directories and ordering that exist when cd runs. A shell function, sourced script, or interactive startup file can set CDPATH, so inspecting only the command text is not enough to explain a different result. When debugging, check both the variable and the actual current directory:

declare -p CDPATH 2>/dev/null || printf '%s\n' 'CDPATH is unset'
pwd -P

pwd -P reports the physical directory reached by the shell. It is useful evidence, but it does not tell you which CDPATH entry caused the lookup; record that configuration separately.

A successful cd may add output

Bash writes the absolute pathname to standard output when cd successfully uses a non-empty directory name from CDPATH. cd - also prints the destination. This is useful at a prompt, where the user can see where a short directory name resolved. It can corrupt a script’s output protocol:

selected=$(cd demo && pwd -P)

If CDPATH finds demo, the captured value can contain both the path printed by cd and the later pwd result. A caller that expects one line now receives two. Suppressing only cd’s standard output preserves its exit status and error stream:

selected=$(cd demo >/dev/null && pwd -P)

That repairs the output shape, but it does not change which directory the relative operand selects. If the operation must resolve from a known base, use an absolute path or remove the ambient search policy for the operation.

Give automation an explicit lookup policy

For a script whose paths are intended to be relative to the caller’s working directory, remove CDPATH in a child shell before changing directories. The subshell keeps the change local and makes the selected working directory explicit:

run_checks() (
    [[ $# -eq 1 && -n $1 ]] || return 2
    unset CDPATH
    cd -- "$1" || return
    exec make test
)

This helper deliberately runs in a subshell, so its cd cannot change the caller’s directory. unset CDPATH prevents a startup environment from redirecting a relative operand elsewhere; cd – protects a path beginning with - from being interpreted as an option. The caller still has to provide the intended base: if a job should always use a repository root, pass or compute that root explicitly rather than trusting the caller’s current directory.

If a script intentionally wants search behavior, make it an argument to the operation instead of an accidental inherited setting. For example, a tool can accept a configured workspace root, construct an absolute directory path, verify that it exists, and then enter that exact path. That design is easier to review than allowing the interactive user’s CDPATH to choose among same-named directories.

Do not globally clear a user’s CDPATH merely to silence one script. At an interactive prompt, the search list can be a useful navigation shortcut. Keep the policy at the boundary that needs it: clear it in a subshell, use a fully qualified path, or redirect the builtin’s output when only silence is needed.

Test both location and output

Build a disposable directory tree with two matching names, one in the current directory and one in a CDPATH entry. Then run the same cd command with CDPATH unset, with a non-empty search entry, and with a null entry. Capture standard output and standard error separately. Confirm:

  1. Which directory became the shell’s current directory.
  2. Whether cd printed a path.
  3. Whether a failed search left the shell in its prior directory.
  4. Whether command substitution receives exactly the output your caller expects.

Repeat from a Bash started with the same startup and environment policy as the actual script. A fresh non-interactive shell is often a better reproduction than testing only in a customized terminal. If a script changes directory, verify its resulting path with pwd -P or an equivalent explicit check before running a destructive or publishing command.

CDPATH is a small variable with two observable effects: it can change the destination of a relative cd, and it can add a successful destination line to standard output. Keep interactive convenience where it helps, but make automation’s directory base and output contract explicit.

Related:

Sources:

Comments