The Directory Stack
Uri Guttman
Navigating through many directory trees and typing in
long pathnames
correctly can be tricky, and many times you have to
jump among several
paths to work on a complex problem. This difficulty
is addressed by the
directory stack feature in the shells. The directory
stack is a dynamic
list of paths that allows users to save commonly used
paths and to jump
among them without retyping any of them. It saves both
typing errors and
navigation time.
I have used the directory stack for years but originally
found it to be
lacking some important features. This article will discuss
the basic
directory stack and its use, as well as my enhancements.
All of these
popular shells: csh, tcsh, zsh and bash offer a built-in
directory
stack. ksh (the Korn Shell) does not have it built-in
but there are a
set of functions to implement the stack published in
the KornShell book
by Bolsky and Korn.
The new functions and aliases have versions written
for all the shells
and are included in this article. The bash version should
run under ksh
and zsh too. (If you use ksh or zsh, please send me
your results.) You
should study your shell's manual page for details on
the built-in
directory commands and its enhancements. Some like zsh
and tcsh have
ways to expand references to entries in the stack and
the bash directory
stack commands can take a negative entry number which
is counted from
the bottom of the stack.
The stack's paths are numbered from the top, starting
with an index of
0, and the top entry is always the current directory.
You can always
execute a chdir command, which will just change the
path on the top of
the stack without affecting the rest of the stack. There
is no physical
limit on how deep the stack can get, but a practical
limit is about 20.
The basic directory stack has three commands, pushd,
popd, and dirs.
These commands manipulate and display the list of directories
in the
stack.
The command dirs will print a short listing of the stack
with the
current directory (top of stack) as the first path.
In the following
example, you are in /usr/local, which is the top of
the stack, and its
index is 0. The other entries /home/uri and /var/tmp
have indices of 1
and 2, respectively.
#dirs
/usr/local /home/uri /var/tmp
The command popd (pop directory) deletes an entry from
the stack. With
no arguments, it pops the top entry and does a cd to
the next entry in
the stack. All the entries move up one entry. If the
new directory at
the top of the stack doesn't exist, or if there is only
one entry, popd
prints an error message. The form of the optional single
argument is +n
where n is a number of an entry in the stack that is
deleted. The top of
the stack is not changed (unless the argument is +0,
which behaves like
the no argument version), so the current directory is
the same as before
the command. After the popd command is done, a dirs
command is executed,
displaying the new directory stack.
In the following examples, the first popd pops the top
of the stack,
which puts you in /home/uri. The second example pops
the third entry
(index 2) and leaves you in /home/uri. The last example
tries to pop an
entry beyond the end of the stack and generates an error
message (this
shows the bash error message).
# dirs
/usr/local /home/uri /var/tmp /etc /export
# popd
/home/uri /var/tmp /etc /export
# popd +2
/home/uri /var/tmp /export
# popd +2
popd: Stack contains only 3 directories
The command pushd adds new entries to the stack and
also rotates the
stack so you can jump to any directory entry. With a
single valid
directory as its argument, pushd pushes down all the
other entries in
the stack and makes the new directory the top entry.
With an argument in
the form +n (as in popd), it rotates the stack from
the top to the
botton until the selected entry is at the top. As with
popd if the +n
value is beyond the end of the stack, an error message
is printed. With
no arguments, pushd swaps the top two entries and leaves
the rest of the
stack unchanged. The no argument version is used to
switch between two
directories. In all cases, it executes a chdir to the
new top of the
stack directory and then executes a dirs command, which
displays the
current stack. If the new top of the stack is not a
valid directory, the
stack is left unchanged, and an error message is printed.
In the first example below, a new directory is pushed
onto the stack.
The second example rotates the stack to the third entry
(index 2) and
makes /home/uri the top of the stack and current directory.
The third
and fourth examples show the no argument version swapping
the top two
stack entries. The last examples demonstrate some illegal
commands:
# dirs
/usr/local /home/uri /export
# pushd /etc
/etc /usr/local /home/uri /export
# pushd +2
/home/uri /export /etc /usr/local
# pushd
/export /home/uri /etc /usr/local
# pushd
/home/uri /export /etc /usr/local
# pushd +4
pushd: Stack contains only 4 directories
#pushd /foo
bash: /foo: No such file or directory
Enhancements
The first set of enhancements that I made (borrowed
from an unknown
author) were aliasing pushd to pd and pushd +2 to pd2
for the values of
2 through 20. This simplifies the typing of repetitive
commands. I left
popd unaliased because it is destructive. Also, in keeping
with short
names, dirs was aliased to ds.
Another short alias (or bash function) that I use does
not affect the
directory stack, but I use it in conjunction with the
stack. This alias
is dset, and it takes one argument, which is a variable
name. It sets
that variable to the current working directory using
the $cwd variable
in the C shell and the $PWD variable in bash. I use
this to remember a
target directory for use in commands like cp and mv.
I pd to the target
directory and then execute a dset command with a single
letter argument,
and then I pd to the source directory. The following
example illustrates
the technique.
# ds
/usr/local/lib/emacs/site-lisp /home/uri /tmp /export
# dset l ; echo $l
/usr/local/lib/emacs/site-lisp
# pd
/home/uri /usr/local/lib/emacs/site-lisp /tmp /export
# cp foo.el $l
When you pd to a path that contains a symbolic link,
you have two paths
to deal with: the real path as reported by pwd and the
virtual path with
the symbolic link. If you were to execute pd .. where
would you be? In
the C shell, you cd to the parent directory in the real
path, while in
bash, you go up the virtual path. Another issue is that
the C shell will
display the virtual path in the directory stack listings,
leading to
this problem. The second pd command looks as though
it should work
(/emacs is a symbolic link to /usr/local/emacs).
# pd /emacs
/emacs /home/uri
# pd ../usr
../usr: No such file or directory
This works in bash because it changes directions relative
to the
directory on the stack, that is, the ../usr path is
relative to /emacs
and not /usr/local/emacs. Check your shell's man page
to see how it
works in this area. A workaround in the C shell is to
set the variable
hardpaths, which causes only real paths to be stored
in the stack. This
doesn't completely fix the problem, but now you can
see where you are
and why the command failed.
After I first started using the directory stack with
these aliases, I
found two major shortcomings with the directory stack.
The first was
that the dirs command printed the directories with blanks
for
separators. I found it difficult to figure out the stack
number of a
directory when the stack was too deep (I can't count
quickly).
The solution is the dl command, which prints a listing
of the stack with
each directory on a single line preceded by its stack
index. The aliases
ds and dl stand for "directory short" and
"directory long,"
respectively. The following is an example of the ds
and dl commands.
Then, I pd to /etc and do another dl command.
# ds
/home/uri /export /etc /usr/local
# dl
0 /home/uri
1 /export
2 /etc
3 /usr/local
# pd2
/etc /usr/local /home/uri /export
# dl
0 /etc
1 /usr/local
2 /home/uri
3 /export
The other major drawback started with ASCII terminals
and then continued
with multiple shells in windows. When you are working
with a directory
stack that you have created and you have to logout (or
your system
crashes), what do you do about the stack? The sd (save
directories)
command writes the current stack to a file, and the
gd (get directories)
retrieves the stack from that file. I use this to save
a stack before
logging out or to copy the stack to a shell in another
window. In this
example, I display the current stack, save it, and pop
it all off. I
then cd somewhere else and finally get back the saved
stack.
# dl
0 /etc
1 /usr/local
2 /home/uri
3 /export
# sd
# popd
/usr/local /home/uri /export
# popd
/home/uri /export
# popd
/export
# cd / ; dl
0 /
# gd ; dl
0 /etc
1 /usr/local
2 /home/uri
3 /export
Listing 1, Listing 2, Listing 3, Listing 4,
Listing 5, and Listing 6
contain the code for these aliases and
functions. Because
the C shell doesn't have true functions, I implemented
dl and sd as
scripts that are sourced into the shell. The paths to
those files
(.dirlist and .savedirs) unfortunately must be hardwired,
but you can
change them to be in a shared place if you wish. The
code for bash, zsh
and ksh uses functions and can just be put into your
.bashrc, .zshrc and
.profile respectively.
About the Author
Uri Guttman has a B.S. in Computer Science and Engineering
from MIT and
is the founder of Sysarch, a systems architecture and
software
engineering firm. He has been hacking and administering
UNIX systems for
over 12 years. Special interests include Perl, WWW,
and solving
difficult computer problems. He can be reached at uri@sysarch.com
and
http://www.sysarch.com.
|