cd

Change directory, and why it cannot be a program

Updated 2026-09-05

cd is built into the shell, and it has to be. A process can change only its own working directory, so a /usr/bin/cd would start, move itself, exit, and leave the shell that called it exactly where it was. The same reasoning explains why a script cannot move the shell that ran it, and why (cd elsewhere && …) in brackets is the safe way to visit a directory.

Most of the flags are about symlinks. cd remembers the path you typed rather than where you landed, so $PWD can name a link while pwd -P names the directory behind it, and cd .. walks back up the path you typed rather than the one on disk. -P asks for the second answer throughout.

There is no manual page, because there is no program. help cd is the documentation.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

site the tree the cp, mv and rm pages use as well, with shortcut added: a symlink to depot/inner, so that the directory it is reached through and the one it really sits in have different names

depot:
total 4
drwxr-xr-x 2 user user 4096 Jun 15 10:00 inner

depot/inner:
total 0

site:
total 16
drwxr-xr-x 2 user user 4096 Jun 15 10:00 assets
drwxr-xr-x 2 user user 4096 Jun 15 10:00 backups
-rw-r--r-- 1 user user   36 Jun  1 09:00 index.html
lrwxrwxrwx 1 user user    9 Jun 18 09:00 latest.css -> style.css
lrwxrwxrwx 1 user user   14 Jun 18 09:00 shortcut -> ../depot/inner
-rw-r--r-- 1 user user   33 Jun 20 09:00 style.css

site/assets:
total 4
-rw-r--r-- 1 user user 35 Jun 10 09:00 logo.svg

site/backups:
total 0

notes.txt one line, beside site/ rather than inside it, and the file the examples try to cd into

Rebuild before deploying.
24 outputs, collapsed by default

cd is part of the shell

Every example below prints a directory name rather than a full path, because the directory this page runs in is named after the page and you would never see it.

Look for the program

type cd; ls /usr/bin/cd

There is no program, and there could not be one. A process can change only its own working directory, so a /usr/bin/cd would move itself, exit, and leave your shell where it was.

Show output
cd is a shell builtin
ls: cannot access '/usr/bin/cd': No such file or directory

Find the documentation

man cd; help cd | head -2

No program means no manual page. help is where the shell documents its own builtins, and man bash carries the same text in its SHELL BUILTIN COMMANDS section.

Show output
No manual entry for cd
cd: cd [-L|[-P [-e]] [-@]] [dir]
    Change the shell working directory.

Watch a subshell's move not escape it

cd /etc; (cd /usr/share && pwd); pwd

The brackets run a child shell, which moves and exits. A script you run does exactly the same thing, which is why cd inside one cannot move the shell that called it, and why (cd elsewhere && …) is the safe way to visit a directory in the middle of a script.

Show output
/usr/share
/etc

Moving around

Go to an absolute path

cd /etc && pwd

A leading / means the path is read from the root of the filesystem, so it means the same thing wherever you started.

Show output
/etc

Go somewhere relative

cd site && basename "$PWD"

Anything else is read from where you already are. basename is here to print the last component, since the full path depends on where the shell started.

Show output
site

Go back up

cd site/assets && cd .. && basename "$PWD"

.. is a real entry in every directory, pointing at its parent, so nothing about it is shell syntax. . is the other one, and cd . does nothing at all.

Show output
site

Go home

cd /etc; cd; basename "$PWD"

cd with no argument goes to $HOME. cd ~ and cd "$HOME" are the same journey spelled two other ways.

Show output
user

Go back where you were

cd /etc; cd /usr/share; cd -

- means the previous directory, and cd prints where it landed so you can see which one that was. Running it twice returns you again, so it toggles.

Show output
/etc

Read the two directories the shell remembers

cd /etc; cd /usr/share; echo "$PWD $OLDPWD"

$PWD is where you are and $OLDPWD is what cd - would use. Both are ordinary variables, so a script can save one and return to it later without a subshell.

Show output
/usr/share /etc

Go up from the root

cd /; cd ..; pwd

/ is its own parent, so this is not an error and does not move. A loop climbing with cd .. until it finds something needs its own stopping condition.

Show output
/

Go into a name with a space in it

mkdir "two words" && cd "two words" && basename "$PWD"

Unquoted, the shell would hand cd two arguments and cd would use the first. Quoting is the same rule as everywhere else, and variables and quoting has the whole of it.

Show output
two words

When it will not go

Each of these returns non-zero and leaves you where you were, which matters more in a script than at a prompt.

Ask for a directory that is not there

cd /nosuchdir; echo "exit $?"

The complaint names the path and the status is 1. Nothing moved.

Show output
bash: line 1: cd: /nosuchdir: No such file or directory
exit 1

Ask for something that is not a directory

cd notes.txt; echo "exit $?"

A different message for a different reason, which is worth reading rather than assuming: this one means the name exists.

Show output
bash: line 1: cd: notes.txt: Not a directory
exit 1

Ask for one you may not enter

chmod 600 site/backups && cd site/backups; echo "exit $?"; chmod 755 site/backups

Entering a directory needs its execute bit, not its read bit. The chmod at the end puts it back. See file permissions explained for why execute means something different on a directory.

Show output
bash: line 1: cd: site/backups: Permission denied
exit 1

Stop a script that could not move

cd /nosuchdir || echo "the script stops here"

Without a guard the script carries on in the wrong directory, and the next rm or tar runs against whatever is there instead. cd somewhere || exit 1 is the line to write.

Show output
bash: line 1: cd: /nosuchdir: No such file or directory
the script stops here

Let set -e do it

set -e; cd /nosuchdir; echo "not reached"

A failed cd ends the script under set -e, so the echo never runs. Debugging and robustness covers what set -e does and does not catch.

Show output
bash: line 1: cd: /nosuchdir: No such file or directory

cd keeps the path you typed rather than the one on disk. Where a symlink is involved those differ, and so does everything downstream of them.

A stack, and a search path

Push a directory and come back

cd /etc; pushd /usr/share; popd; pwd

pushd moves and remembers, popd returns and forgets. Each prints the stack, which is why /usr/share /etc appears before the return.

Show output
/usr/share /etc
/etc
/etc

Read the stack without moving

cd /etc; pushd /usr/share >/dev/null; dirs

dirs prints the stack alone, current directory first. dirs -v numbers the entries, and cd ~2 jumps to one by number.

Show output
/usr/share /etc

Give cd a search path

CDPATH=/usr; cd share && pwd

CDPATH is a list of directories a relative name is looked up in, so cd share reaches /usr/share from anywhere. Note the path printed twice: cd announces where it went whenever CDPATH was what found it.

Show output
/usr/share
/usr/share

Watch CDPATH corrupt a captured path

CDPATH=/usr; d=$(cd share && pwd); echo "d is: [$d]"

The announcement goes to standard output, so $( ) captures it along with the answer and the variable holds two lines. This is why CDPATH belongs in an interactive shell's configuration and never in a script.

Show output
d is: [/usr/share
/usr/share]