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.
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]
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]
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:
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.
 |  |  | 4.6. Command Substitution |  | 5. Flow Control |
Copyright © 2003 O'Reilly & Associates. All rights reserved.
|