Understanding Pushd and Popd

One of the more powerful, and under-used, under-appreciated, and little-understood capabilities of text-based command line environments is the ability to interact with the filesystem as a stack with the pushd, popd, and dirs commands. This article introduces the idea, and the commands, explains how it works, and how to benefit from it.

The Stack Metaphor

Have you ever been in a canteen or restaurant where plates are dispensed from some kind of device, from which automatically another plate emerges? That is, you take a plate, and a new plate pops up in its place? There's a similar thing for dispensing trays, in the same kind of context. In both cases, we have a stack of plates or trays inside a sprung container, which forces the items up, such that as you ease a plate out of the container it pops off, and the one below rises up to replace it, until the stack is empty. If you need to add plates to the stack you push them into the container, resisting the spring. This idea is the basis of the stack metaphor in computer science.

A stack is an ordered data structure representing a collection of elements. Just like the plates, or trays, there are two basic operations associated with a stack - pushing and popping.

Thinking again about the plate dispenser, it's clear that any operations on the stack can only be applied from the top of the stack. We can't get to the plates at the bottom of the stack - we can push more plates onto the stack, or we can allow or encourage a plate to pop off the stack.

Naturally, with all metaphors, this doesn't cover every eventuality or implementation detail, but I find having a visual representation in my mind when thinking about stacks to be a very useful one.

A Simple Stack

Most of the time when we operate on a stack, we're operating on something which we visualise left-to-right, rather than top-to-bottom - for example an array or a list. In this case, the top of our stack becomes the left-most item. Here's an example in Common LISP, which has macros for the push and pop operations:

>(setf example_stack (list 'one 'two ' three))

(ONE TWO THREE)

>(pop example_stack)

ONE

>example_stack

(TWO THREE)

>(push 'four example_stack)

(FOUR TWO THREE)

The Stack Metaphor in the Shell

The stack metaphor is also present in text-based command line shells. On unix-like systems, the C, tcsh, bash, and zsh shells all include mechanisms for treating directories in the filesystem tree as a stack, and on Windows, both legacy DOS/CMD shells and Powershell expose this capability.

In this article I'm going to cover the bash implementation. The other UNIX shells have similar implementations. Consult the relevant manual page to determine the exact differences, if any.

Powershell provides the Push-Location and Pop-Location cmdlets (which are aliased to pushd and popd), and more information on these can be found by running Get-Help Push-Location -Full -Online.

If you're working in an environment which involves a lot of moving around between directories, within a filesystem, it can quickly get tedious, and wasteful, to keep typing or even tab-completing filesystem paths. Its not uncommon to have a work environment that looks a little like this:

bash-3.2$ tree stacks/
stacks/
├── atalanta-plans
│   ├── exim
│   │   └── config
│   ├── queen
│   │   └── config
│   └── squid
│     └── config
└── core-plans
  ├── exim
  │   └── config
  ├── nginx
  │   └── config
  ├── openssl
  │   └── config
  └── wordpress
    └── config

This represents a greatly simplified snapshot of some of my current projects, automating applications with habitat.sh. In this case, I'm working on a plan which doesn't currently exist in the upstream core-plans, but which I might want to contribute, a plan which does exist in core-plans, but which I need to make significant changes that probably won't be pushed upstream, and a plan for internal software I'm writing which won't be made available any time soon.

In the core-plans directory there are actually hundreds of possible plans I might want want to refer or crib from, and I quite often want to switch between two or three directories. Similarly, I have more directories and a more complex directory structure than I've indicated, and I need to navigate it in a number of different ways.

Most people would tend to do this with plenty of ../../.. behaviour, or cd ; cd /path/to/somewhere. A slightly more advanced manouever would be to run cd - to go to the previously visited directory, but by treating the filesystem, and places visited, as a stack, we open up possibilities for a more efficient workflow.

The idea is that instead of having only two options - the current directory, and the directory you were in immediately before - you can maintain a stack of directories, and visit whichever you want from the stack. That stack can be arbitrarily large, but even if it were only one directory deep it would still be useful.

The Pushd, Popd, and Dirs Commands

The directory stack implementation in bash is provided by three shell 'builtins' - dirs, pushd and popd. In the following paragraphs I will refer to them as commands, but for the sake of accuracy, these commands are actually contained within the shell itself, and the shell executes the command directly, without invoking another program.

For reference, consult Bash's directory stack builtin documentation.

Dirs - examining the stack

The dirs command shows us the stack. The stack always contains the current directory. This is a fundamental rule for the directory stack, which I will emphasise a number of times:

The top of the stack is always the current working directory, and the current working directory is always the top of the stack.

This is the case regardless of whether any directory stack builtins have been used. Changing between directories with the cd directory simply replaces the single item on the stack.

As the stack grows, the dirs command takes on extra significance, as it might be desirable to inspect the stack in a different format, or reset it.

Here's a larger stack:

/etc/apt/apt.conf.d /var/log/apt /etc ~

And here we see it in long format, with directories expanded, and the index:

dirs -l -v
 0 /etc/apt/apt.conf.d
 1 /var/log/apt
 2 /etc
 3 /root

You can also use dirs to peek at the stack positionally:

# dirs +1
/var/log/apt
# dirs -1
/etc

Here we looked at stack one down from the top, and one up from the bottom. Note that in this case dirs +2 will be the same as dirs -1.

Finally, if we're not interested in the directories on the stack any more, we can just clear the stack:

# dirs -c
# dirs
/etc/apt/apt.conf.d

Notice again that the top of the stack is the current working directory.

Pushd - adding to the stack

We add to the stack with the pushd command. This has two forms - one which simply adds a directory to the stack, and one which adds a directory to the stack and changes into that directory.

$ pwd
/Users/stephen.nelsonsmith
$ dirs -c
$ dirs
~
$ pushd stacks/atalanta-plans/
~/stacks/atalanta-plans ~
$ pushd ../core-plans/nginx/
~/stacks/core-plans/nginx ~/stacks/atalanta-plans ~
$ pushd ~/.vim
~/.vim ~/stacks/core-plans/nginx ~/stacks/atalanta-plans ~

You can see that instead of typing cd we can use pushd to change to a directory, and also add that directory to the stack, rather than simply replace it in a single item stack.

Note that when the pushd command succeeds, a dirs command also runs, to show the state of the stack.

The second mode is invoked by supplying the -n switch to pushd, which will simply populate the stack with a directory:

$ pushd -n /etc
~/.vim /etc ~/stacks/core-plans/nginx ~/stacks/atalanta-plans ~
$ pushd -n ~/stacks/atalanta-plans/queen/
~/.vim ~/stacks/atalanta-plans/queen/ /etc ~/stacks/core-plans/nginx ~/stacks/atalanta-plans ~

Note here that the rule that the current working directory is always at the top of the stack trumps the push operation - so in practice push -n will always insert the directory to the second-top position. That's slightly counter-intuitive, but as long as you remember that the current working directory is always the top of the stack, you won't go far wrong.

There's one more operation which can be carried out by pushd, and that's to rotate the stack.

To visialise this, let's create a structure based on the numbers 1 - 5:

$ cd stacks
$ mkdir {one,two,three,four,five}
$ dirs -c
$ ls
atalanta-plans	core-plans	five		four		one		three		two
$ dirs
~/stacks
$ pushd -n ~/stacks/five
~/stacks ~/stacks/five
$ pushd -n ~/stacks/four
~/stacks ~/stacks/four ~/stacks/five
$ pushd -n ~/stacks/three
~/stacks ~/stacks/three ~/stacks/four ~/stacks/five
$ pushd -n ~/stacks/two
~/stacks ~/stacks/two ~/stacks/three ~/stacks/four ~/stacks/five
$ pushd -n ~/stacks/one
~/stacks ~/stacks/one ~/stacks/two ~/stacks/three ~/stacks/four ~/stacks/five

Now let's rotate the stack. We do this by specify the position in the stack as an argument, for example +2 refers to position 2 (counting from zero) from the top/left, or -2 (start at the bottom and two up). This becomes the new head of the stack, and the stack rotates around in the same order. You can see this here:

$ pushd +1
~/stacks/one ~/stacks/two ~/stacks/three ~/stacks/four ~/stacks/five ~/stacks
$ pwd
/Users/stephen.nelsonsmith/stacks/one
$ dirs -v
 0 ~/stacks/one
 1 ~/stacks/two
 2 ~/stacks/three
 3 ~/stacks/four
 4 ~/stacks/five
 5 ~/stacks

We rotated the stack - ~/stacks went from the top to the bottom, and ~/stacks/one became the top, and, following our golden rule, ~/stacks/one is now the current working directory.

$ pushd -2
~/stacks/four ~/stacks/five ~/stacks ~/stacks/one ~/stacks/two ~/stacks/three

We now specified that the top of the stack (and thus the current working directory) should be two from the end/right.

$ dirs -v
 0 ~/stacks/four
 1 ~/stacks/five
 2 ~/stacks
 3 ~/stacks/one
 4 ~/stacks/two
 5 ~/stacks/three

Let's get ~stacks back to the top:

$ pushd +2
~/stacks ~/stacks/one ~/stacks/two ~/stacks/three ~/stacks/four ~/stacks/five
$ dirs -v
 0 ~/stacks
 1 ~/stacks/one
 2 ~/stacks/two
 3 ~/stacks/three
 4 ~/stacks/four
 5 ~/stacks/five

The dirs directory is useful in this context, as it allows you to test what would come to the top.

$ dirs +3
~/stacks/three
$ pushd +3
~/stacks/three ~/stacks/four ~/stacks/five ~/stacks ~/stacks/one ~/stacks/two
$ dirs -4
~/stacks/four
$ pushd -4
~/stacks/four ~/stacks/five ~/stacks ~/stacks/one ~/stacks/two ~/stacks/three

popd - remove items from the stack

The final operation is removing items from the stack. This is accomplished with the popd builtin. Again, return to the image of the spring-loaded plate dispenser. When we issue popd, the top plate has left the stack. Some hungry patron of the canteen is loading it up with delicious, nutritious vegan delicacies, and the plate underneath is the new top plate.

Now, remember? The top of the stack is always the current working directory, and the current working directory is always the top of the stack. So the directory that pops off has gone. It doesn't matter where it went, or what it was. We'll arrive in the new top directory, or, if you like, in the directory that was at position 1 in the stack before we called popd.

Let's see this in action:

$ dirs -c
$ ls
atalanta-plans	core-plans	five		four		one		three		two
$ pushd atalanta-plans/squid/config/
~/stacks/atalanta-plans/squid/config ~/stacks
$ # do some work
$ pushd ../../queen/
~/stacks/atalanta-plans/queen ~/stacks/atalanta-plans/squid/config ~/stacks
# bit more work
# compare something in core-plans
$ cd ~/stacks/core-plans/nginx/
$ dirs -v
 0 ~/stacks/core-plans/nginx
 1 ~/stacks/atalanta-plans/squid/config
 2 ~/stacks
$ popd
~/stacks/atalanta-plans/squid/config ~/stacks

Here we saw some pushd action. We started off in the squid config directory, then went to a number of other places before returning immediately to the squid config directory by popping it off the stack.

Did you spot the subtelty here? We started with a clear stack. We visited our squid config dir (and added it to the stack). The stack started with the current working directory (which was ~/stacks), and we added ~/stacks/atalanta-plans/squid/config. Next we went to the queen directory (and again pushed it to the stack). The stack is now three deep. At this point however, we changed directory to the nginx core plans directory. Note we didn't pushd to it. The current working directory is always the top of the stack, so we shouldn't be surprised to see ~/stacks/core-plans/nginx at the top, but you might be a little surprised that this replaced ~/stacks/atalanta-plans/queen. Watch out for this - it's a little unexpected, and can catch you out.

Once we cd to the nginx directory, the stack looked like this:

$ dirs -v
 0 ~/stacks/core-plans/nginx
 1 ~/stacks/atalanta-plans/squid/config
 2 ~/stacks

And the popd command placed us into the directory at position 1 in the stack.

$ popd
~/stacks/atalanta-plans/squid/config ~/stacks

Unsurprisingly, popd also takes the same positional parameters as pushd and dirs, although unlike pushd, when invoked in this way, the directory is not changed. Again, counting is from the left, where the top, or leftmost entry is zero, (+0, +1, +2) or from the right, where again, the bottom, or rightmost entry is zero (-0, -1, -2). Calling popd in this manner will pop off the directory at that point in the stack. Here is where the metaphor breaks down, as we've effectively reached into the spring-loaded container and slid one of the plates out!

Returning to our numbered example stack:

$ dirs -p
~/stacks
~/stacks/one
~/stacks/two
~/stacks/three
~/stacks/four
~/stacks/five

If we wish to remove ~/stacks/three, this is at position 3, from the top, or -2 from the bottom of the stack, so popd -2 or popd +3 will remove it.

$ popd +3
~/stacks ~/stacks/one ~/stacks/two ~/stacks/four ~/stacks/five

We see that ~/stacks/three has vanished, but we're still in the ~/stacks directory.

popd -3 will now remove ~/stacks/one

$ popd -3
~/stacks ~/stacks/two ~/stacks/four ~/stacks/five

We can remove the last entry with popd -0

$ popd -0
~/stacks ~/stacks/two ~/stacks/four

The only exception to the rule that the positional parameters don't change directory is +0, which removes the top of the stack. Of course this is functionally equivalent to calling popd with no arguments, and if you remember the golden rule, you won't be surprised, because, of course, the current working directory is always the top of the stack and the top of the stack os always the current working directory.

Finally, popd does have a -n switch, which, like pushd mutates the stack, but doesn't change directory. However, I can't think of a use for this that isn't accomplished by popd with a positional parameter.

Putting it all together

It's hard to recommend a specific workflow, or pass on general tips for how to make the most of these capabilities. The best way is for you to start to experiment with them in your own terminal, however, here are a few ideas and suggestions:

Probably the simplest change to consider is using pushd when you might use cd. This won't change your workflow, but it will enable you to build a potentially useful stack.

The next thing to introduce would be periodically to inspect or clear the stack with dirs -v and dirs -c, and to change to the candidate top directory using popd.

Rotating the stack, and using positional arguments can be added at the point of need.

I hope this has been a useful overview, and it will encourage you to find new ways to be more effective and efficient on the command line.

Show Comments