Why this matters
Working in the shell means constantly moving between directories: into a project, over to the logs, back home. Being quick and exact with paths saves time, and more importantly it prevents the classic accident of running a command in the wrong place. Scripts also depend on knowing where they are: a script that assumes it is started from one particular directory breaks the first time someone runs it from another.
pwd: where am I?
Every process has a current working directory (CWD), kept by the kernel. Relative paths are resolved from it. pwd prints the shell's:
$ pwd
/work
The prompt in this sandbox also shows it (learner@sandbox:/work$), and abbreviates your home directory as ~.
cd: change directory
cd takes one path, absolute or relative, and makes it the shell's new current directory:
$ cd /etc
$ pwd
/etc
$ cd apt
$ pwd
/etc/apt
$ cd ..
$ pwd
/etc
$ cd ../usr/share
$ pwd
/usr/share
Remember from lesson 2 of module 1 that cd is a builtin. It calls the chdir system call in the shell's own process, so the change sticks for every command you run afterwards (each one is forked from the shell and inherits the new directory).
Shortcuts
| Command | Goes to |
|---|---|
cd | your home directory (the value of $HOME) |
cd ~ | the same: the shell replaces ~ with your home directory |
cd ~/notes | notes inside your home |
cd - | the previous directory (and prints it) |
cd .. | the parent directory |
cd / | the root |
$ cd /var/log
$ cd /etc
$ cd -
/var/log
$ cd -
/etc
$ cd
$ pwd
/home/learner
cd - is wonderful for bouncing between two places. It works because bash remembers the previous directory in the variable OLDPWD.
The tilde is expanded by bash
~ is not a directory name the kernel knows about. bash replaces a ~ at the start of a word with the value of $HOME before running the command:
$ echo ~
/home/learner
$ echo ~/notes
/home/learner/notes
$ echo "~"
~
Inside quotes it is left alone. This is your first example of expansion: bash rewriting the line before running it. Module 7 is all about this.
Relative paths, carefully
From /work/maze/north/cellar:
/work
└── maze
├── north
│ ├── attic ../attic (sibling: up one, down one)
│ └── cellar . (here)
└── east ../../east (up two, down one)
. means "this directory". You rarely need it in cd, but it matters in two places: running a program from the current directory (./script.sh, because . is not in PATH), and making a name that starts with - look less like an option (./-file).
What happens underneath: ., .. and symbolic links
. and .. are real entries stored in every directory, pointing at the directory itself and its parent. The kernel follows them like any other name.
There is one subtlety. When a path goes through a symbolic link (a shortcut to another directory, covered in module 3), bash's cd normally keeps track of the path as you typed it (the logical path), so cd .. takes you back the way you came. The kernel's view (the physical path) may be different. pwd -P shows the physical one:
$ cd /bin
$ pwd
/bin
$ pwd -P
/usr/bin
On this Debian system /bin is a symbolic link to /usr/bin. For everyday work the logical view is what you want; just know the two exist when paths look surprising.
Listing as you go: ls and tree
ls with no argument lists the current directory; give it a path to list somewhere else without moving:
$ ls /work/maze
east north south west
$ ls maze/north
attic cellar
tree draws a whole subtree, which is great for getting your bearings:
$ tree maze
maze
├── east
│ └── empty.txt
├── north
│ ├── attic
│ │ └── box.txt
│ └── cellar
...
tree -d shows only directories, tree -L 2 only two levels deep.
Use Tab completion constantly: cd ma<Tab> becomes cd maze/, and cd maze/<Tab><Tab> lists the choices.
Paths in scripts
A script inherits the current directory of whoever started it. So a script that does cat data.txt works only if it is run from the directory containing data.txt. Two habits:
- In scripts, prefer absolute paths, or paths built from a known base directory (you will learn to find the script's own directory later).
- Always check that a
cdworked before doing anything destructive:cd /some/dir && rm old.log.
A cd inside a script changes the directory of the script's process only. When the script ends, your interactive shell is exactly where it was, for the same fork-and-exec reason that cd has to be a builtin.
Common mistakes
cdinto a file.cd /etc/hostnamegivesbash: cd: /etc/hostname: Not a directory.- Forgetting the path is relative.
cd etcfrom/workgivesNo such file or directory; you meant/etc. - Spaces in directory names.
cd My Documentsgivesbash: cd: too many arguments. Quote it:cd 'My Documents', or let Tab completion insert the backslash:cd My\ Documents. - Quoting the tilde.
cd "~/notes"looks for a directory literally named~. Writecd ~/notesorcd "$HOME/notes". - Thinking a script's
cdmoves you. It cannot; it moves the script's own process.
Recap
- The current working directory belongs to each process;
pwdshows it and the builtincdchanges the shell's. cdalone orcd ~goes home,cd -goes back,cd ..goes up.~is expanded by bash to$HOME, but not inside quotes.- Relative paths are resolved from the current directory;
.is here,..is the parent. pwd -Pshows the physical path when symbolic links are involved.
Going further: try pushd /etc, pushd /var/log, dirs, popd: a stack of directories built into bash. help pushd explains it.