Skip to content

Latest commit

Β 

History

History
358 lines (272 loc) Β· 9.93 KB

File metadata and controls

358 lines (272 loc) Β· 9.93 KB

cd β€” The Complete Reference

Change the current working directory The most frequently typed command in a terminal β€” and the only one that cannot exist as a regular program. cd is a shell builtin, always.


Table of Contents


What is cd?

cd changes the current working directory of the shell process. Every process on Linux has a working directory β€” the directory used as the base when resolving relative paths.

cd is specified by POSIX and is present in every Unix shell: bash, zsh, dash, ksh, fish, and others. The behavior is mostly identical across shells, with small additions in bash and zsh.


Why cd Must Be a Builtin

This is the most fundamental fact about cd β€” and a classic interview question.

Every process on Linux has its own working directory, stored in the kernel. When a shell runs an external command, it forks a child process. The child can change its own working directory, but that change does not affect the parent (the shell).

shell (pid=1000, cwd=/home/alice)
  └── fork β†’ child (pid=1001, cwd=/home/alice)
                └── chdir("/tmp")    ← only changes child's cwd
                └── exit
shell (pid=1000, cwd=/home/alice)   ← unchanged!

If cd were an external program, running it would:

  1. Fork a child process
  2. Child changes its own cwd to /tmp
  3. Child exits
  4. Shell's cwd is still /home/alice β€” nothing changed

Solution: cd must run inside the shell process itself, using chdir() directly. That's what a builtin is β€” a command implemented inside the shell, not a separate executable.

type cd
# cd is a shell builtin

which cd        # may return nothing, or a stub
/usr/bin/cd     # this exists but is useless as a standalone program

How cd works internally

When you type cd /tmp:

  1. Shell parses the command, recognizes cd as a builtin
  2. Shell calls chdir("/tmp") syscall directly (no fork)
  3. On success: shell updates $PWD and $OLDPWD environment variables
  4. On failure: shell prints an error, $PWD unchanged
chdir(path)  β†’  update $OLDPWD = $PWD  β†’  update $PWD = new path

The chdir() syscall:

  • Takes an absolute or relative path
  • Updates the process's working directory in the kernel
  • Returns 0 on success, -1 on error (ENOENT, ENOTDIR, EACCES, etc.)

$PWD is maintained by the shell, not the kernel:

  • The kernel tracks the real (physical) path
  • The shell tracks the logical path (preserving symlink names)
  • pwd (builtin) shows $PWD (logical); pwd -P shows the kernel's real path

Syntax

cd [-L|-P] [-e] [-@] [directory]
  • With no argument β†’ goes to $HOME
  • With - β†’ goes to previous directory ($OLDPWD)
  • directory can be absolute, relative, or a $CDPATH shortcut

Special Arguments

cd with no argument β†’ home directory

cd          # same as: cd $HOME  or  cd ~
cd ~        # explicit home
cd ~/docs   # subdirectory of home

cd - β†’ previous directory

cd /var/log
cd /etc
cd -        # back to /var/log
cd -        # back to /etc
# Toggles between two directories
# Also prints the directory it switched to

cd ~username β†’ another user's home

cd ~root        # /root
cd ~alice       # /home/alice (if you have permission)
cd ~www-data    # web server user's home

cd .. β†’ parent directory

cd ..           # one level up
cd ../..        # two levels up
cd ../../etc    # up two, then into etc

cd / β†’ root

cd /            # filesystem root

cd . β†’ current directory (no-op)

cd .            # does nothing (stays in same dir)

All Options

-L β€” Logical (default)

Follow the logical path. Symlink names are preserved in $PWD.

ln -s /var/log /tmp/logs
cd /tmp/logs       # $PWD = /tmp/logs  (logical)
cd -L /tmp/logs    # same

-P β€” Physical

Resolve all symlinks. $PWD shows the real path.

ln -s /var/log /tmp/logs
cd -P /tmp/logs    # $PWD = /var/log  (physical/real)

-e β€” Exit with error if -P fails (bash 4+)

cd -Pe /tmp/logs   # if physical path can't be determined, return error

-@ β€” macOS/zsh: open extended attributes

cd -@ file.txt     # treats file's extended attribute namespace as a directory
# macOS/zsh specific β€” rarely used

Environment Variables

$HOME

The default destination for bare cd. Set at login by PAM/the system.

echo $HOME      # /home/alice
cd              # goes to $HOME
cd ~            # same

# Temporarily change home:
HOME=/tmp cd    # doesn't work β€” builtin doesn't take env prefix
export HOME=/tmp && cd   # changes home for session

$PWD

Current working directory β€” maintained by the shell (not the kernel).

echo $PWD       # /home/alice/projects
pwd             # same (reads $PWD)
pwd -P          # physical path (resolves symlinks, asks kernel)

$OLDPWD

The previous working directory β€” set by cd each time you change directories.

cd /var/log
cd /etc
echo $OLDPWD    # /var/log
cd -            # uses $OLDPWD to go back

$CDPATH

A colon-separated list of directories to search when cd is given a relative path that doesn't exist locally.

export CDPATH=".:$HOME:$HOME/projects:/var"

cd log          # tries ./log, then ~/log, then ~/projects/log, then /var/log
                # /var/log exists β†’ goes there!
                # Also prints the full path it resolved to

$CDPATH is powerful but can cause surprising behavior β€” you type cd src expecting ./src but end up in ~/projects/src.


Logical vs Physical Directory

This distinction matters when symlinks are involved.

mkdir -p /real/path
ln -s /real/path /tmp/link

# Logical (default: -L)
cd /tmp/link
pwd             # /tmp/link       ← shell's $PWD (preserves symlink name)
pwd -P          # /real/path      ← kernel's real path

# Physical (-P)
cd -P /tmp/link
pwd             # /real/path      ← $PWD is updated to real path

# Traversal difference:
cd /tmp/link
cd ..
pwd             # /tmp            ← went up from the logical path

cd -P /tmp/link
cd ..
pwd             # /real           ← went up from the real path

cd in Different Shells

bash

  • All standard options: -L, -P, -e
  • Supports $CDPATH
  • cd - prints destination

zsh

  • All bash options plus -@ (extended attributes on macOS)
  • $CDPATH supported
  • AUTO_CD option: type a directory name without cd to navigate
    setopt AUTO_CD
    /etc          # same as: cd /etc
    ..            # same as: cd ..
  • PUSHD_SILENT, AUTO_PUSHD: automatically push to directory stack

fish

  • No -L/-P flags (uses builtin cd)
  • cdh shows recent directory history interactively
  • Abbreviation system can make .. work as cd ..

dash / sh

  • Only -L and -P (POSIX minimum)
  • No $CDPATH required by POSIX (but usually supported)

ksh

  • Same as bash options
  • cd old new β€” replaces old with new in current path:
    pwd           # /home/alice/project/src
    cd src lib    # goes to /home/alice/project/lib

Modern Alternatives

Tools that enhance or replace cd with smarter navigation:

z / zoxide β€” frecency-based jumping

# After visiting /home/alice/projects/myapp a few times:
z myapp         # jumps to /home/alice/projects/myapp
z proj          # fuzzy matches most frequent dir with "proj"

# zoxide (Rust, faster z):
eval "$(zoxide init bash)"   # add to ~/.bashrc
z myapp
zi myapp        # interactive selection with fzf

pushd / popd β€” directory stack

pushd /var/log    # go to /var/log AND push current dir to stack
pushd /etc        # go to /etc AND push /var/log
popd              # return to /var/log (pop from stack)
popd              # return to original dir
dirs              # show directory stack
dirs -v           # show with index numbers
cd ~2             # go to index 2 in stack (zsh)

fzf β€” fuzzy directory finder

# Add to ~/.bashrc:
bind '"\C-f": "cd $(find . -type d | fzf)\n"'
# Ctrl+F β†’ fuzzy search all subdirectories

autojump β€” similar to z

j myapp         # jump to most used dir matching "myapp"
jc myapp        # jump to child directory matching "myapp"
jo myapp        # open directory in file manager

broot β€” interactive directory browser

br              # open interactive tree, navigate and cd

Related Commands

Command Relation
pwd Print current working directory ($PWD or physical)
pushd Like cd but pushes to directory stack
popd Return to previous directory from stack
dirs Show directory stack
ls List contents of a directory
mkdir Create a directory to cd into
realpath Resolve a path to its absolute physical form
readlink -f Resolve symlinks in a path
z / zoxide Smart frecency-based directory jumping

See also: examples.md Β· edge-cases.md Β· interview-questions.md