## 4.7. Advanced Examples: pushd and popd

We conclude this chapter with a couple of functions that you may find handy in your everyday Unix use. They solve the problem presented by Task 4-7.

> #### Task 4-7 In the C shell, the commands pushd and popd implement a stack of directories that enable you to move to another directory temporarily and have the shell remember where you were. The dirs command prints the stack. The Korn shell does not provide these commands. Implement them as shell functions.

We start by implementing a significant subset of their capabilities and finish the implementation in Chapter 6. (For ease of development and explanation, our implementation ignores some things that a more bullet-proof version should handle. For example, spaces in filenames will cause things to break.)

If you don't know what a stack is, think of a spring-loaded dish receptacle in a cafeteria. When you place dishes on the receptacle, the spring compresses so that the top stays at roughly the same level. The dish most recently placed on the stack is the first to be taken when someone wants food; thus, the stack is known as a "last-in, first-out" or LIFO structure. (Victims of a recession or company takeovers will also recognize this mechanism in the context of corporate layoff policies.) Putting something onto a stack is known in computer science parlance as pushing, and taking something off the top is called popping.

A stack is very handy for remembering directories, as we will see; it can "hold your place" up to an arbitrary number of times. The `cd -` form of the cd command does this, but only to one level. For example: if you are in firstdir and then you change to seconddir, you can type `cd -` to go back. But if you start out in firstdir, then change to seconddir, and then go to thirddir, you can use `cd -` only to go back to seconddir. If you type `cd -` again, you will be back in thirddir, because it is the previous directory.[64]

> [64] Think of `cd -` as a synonym for `cd $OLDPWD`; see the previous chapter.

If you want the "nested" remember-and-change functionality that will take you back to firstdir, you need a stack of directories along with the dirs, pushd and popd commands. Here is how these work:[65]

> [65] We've done it here differently from the C shell. The C shell pushd pushes the initial directory onto the stack first, followed by the command's argument. The C shell popd removes the top directory off the stack, revealing a new top. Then it cds to the new top directory. We feel that this behavior is less intuitive than our design here.

- pushd dir does a cd to dir and then pushes dir onto the stack.

- popd does a cd to the top directory, then pops it off the stack.

For example, consider the series of events in Table 4-12. Assume that you have just logged in and that you are in your home directory (/home/you).

We will implement a stack as an environment variable containing a list of directories separated by spaces.

#### Table 4-12. pushd/popd example

Command | Stack contents (top on left) | Result directory
--- | --- | ---
`pushd fred` | /home/you/fred | /home/you/fred
`pushd /etc` | /etc /home/you/fred | /etc
`cd /usr/tmp` | /etc /home/you/fred | /usr/tmp
`popd` | /home/you/fred | /etc
`popd` | (empty) | /home/you/fred

Your directory stack should be initialized to your home directory when you log in. To do so, put this in your .profile:

>     DIRSTACK="$PWD"
>     export DIRSTACK

Do not put this in your environment file if you have one. The export statement guarantees that `DIRSTACK` is known to all subprocesses; you want to initialize it only once. If you put this code in an environment file, it will get reinitialized in every interactive shell subprocess, which you probably don't want.

Next, we need to implement dirs, pushd, and popd as functions. Here are our initial versions:

>     function dirs {        # print directory stack (easy)
>         print $DIRSTACK
>     }

>     function pushd {       # push current directory onto stack
>         dirname=$1
>         cd ${dirname:?"missing directory name."}
>         DIRSTACK="$PWD $DIRSTACK"
>         print "$DIRSTACK"
>     }

>     function popd {        # cd to top, pop it off stack
>         top=${DIRSTACK%% *}
>         DIRSTACK=${DIRSTACK#* }
>         cd $top
>         print "$PWD"
>     }

Notice that there isn't much code! Let's go through the functions and see how they work. dirs is easy; it just prints the stack. The fun starts with pushd. The first line merely saves the first argument in the variable dirname for readability reasons.

The second line's main purpose is to change to the new directory. We use the `:?` operator to handle the error when the argument is missing: if the argument is given, the expression `${dirname:?"missing directory name."}` evaluates to `$dirname`, but if it is not given, the shell prints the message `ksh: pushd: line 2: dirname: missing directory name.` and exits from the function.

The third line of the function pushes the new directory onto the stack. The expression within double quotes consists of the full pathname for the current directory, followed by a single space, followed by the contents of the directory stack (`$DIRSTACK`). The double quotes ensure that all of this is packaged into a single string for assignment back to `DIRSTACK`.

The last line merely prints the contents of the stack, with the implication that the leftmost directory is both the current directory and at the top of the stack. (This is why we chose spaces to separate directories, rather than the more customary colons as in `PATH` and `MAILPATH`.)

The popd function makes yet another use of the shell's pattern-matching operators. The first line uses the `%%` operator, which deletes the longest match of " `*`" (a space followed by anything). This removes all but the top of the stack. The result is saved in the variable `top`, again for readability reasons.

The second line is similar, but going in the other direction. It uses the `#` operator, which tries to delete the shortest match of the pattern "`*` " (anything followed by a space) from the value of `DIRSTACK`. The result is that the top directory (and the space following it) is deleted from the stack.

The third line actually changes directory to the previous top of the stack. (Note that popd doesn't care where you are when you run it; if your current directory is the one on the top of the stack, you won't go anywhere.) The final line just prints a confirmation message.

This code is deficient in the following ways: first, it has no provision for errors. For example:

- What if the user tries to push a directory that doesn't exist or is invalid?

- What if the user tries popd and the stack is empty?

Test your understanding of the code by figuring out how it would respond to these error conditions. The second deficiency is that the code implements only some of the functionality of the C shell's pushd and popd commands -- albeit the most useful parts. In the next chapter, we will see how to overcome both of these deficiencies.

The third problem with the code is that it will not work if, for some reason, a directory name contains a space. The code will treat the space as a separator character. We'll accept this deficiency for now. However, when you read about arrays in Chapter 6, think about how you might use them to rewrite this code and eliminate the problem.

Copyright © 2003 O'Reilly & Associates. All rights reserved.