cd
Change directory, and why it cannot be a program
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.
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
Symlinks, and the two answers to ...
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.
See where you are and where you really are
cd site/shortcut && basename "$PWD" && basename "$(pwd -P)"
shortcut is a symlink to depot/inner. $PWD holds the path you walked and pwd -P resolves every link in it, so the two disagree by design.
Show output
shortcut
inner
Go up from a symlinked directory
cd site/shortcut && cd .. && basename "$PWD"
Back to site, because that is where you came from. The shell is retracing the path you typed, not the one on disk.
Show output
site
Go up the way the filesystem sees it
cd site/shortcut && cd -P .. && basename "$PWD"
-P (--physical) resolves the link first, so .. is depot, the real parent. A script walking upward with cd .. and one using cd -P .. visit different directories.
Show output
depot
Land on the real directory in the first place
cd -P site/shortcut && basename "$PWD"
-P on the way in means $PWD never holds the link at all, so everything afterwards agrees with the filesystem. set -o physical makes it the default for the whole shell.
Show output
inner
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]