Cover V05, I04
Article
Listing 1
Listing 2
Listing 3
Listing 4
Listing 5
Listing 6

apr96.tar


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.