Fun with pushd

3 minute read

Here’s about as esoteric a post as I ever write, my love of pushd and it’s little abused directory stack. If you don’t live on the command line, move along, there is nothing to see here. Both Bash and TCSH have a built in command pushd and a companion popd. Both shells work the same way, pushd somewhere saves (pushes) the current working on to a stack and then cds to somewhere. popd reverses the process, popping the newest directory off the stack and cding to it.

pushd predates modern window systems. It’s raison d’être is to let you get back to some deeply nested path when you need to change directories. Nowadays, you could just open another window but, even in the modern error, pushd still has a trick up it sleeve, it’s stack.

First, being lazy, I alias it to pd:

BASH:

alias pd=pushd

TCSH

alias pd pushd

My trick is to use the stack to copy or move files to some deeply nested directory. Commonly, I’m at work on a project deep in some directory structure. I download a new asset and I need to copy it in to the project.

I could switch the download directory, taking advantage of the save dialog’s ⌘-G shortcut. But when I do, the next time I save something I end up forgetting to switch back, saving the new download in that directory, and not being able to find it.

Another simple option is to take advantage of OS X’s open command. Running:

open -R ~/Downloads/the-file
open the-destination-directory

will open two Finder windows, one with the download file highlighted and one with the destination folder open. After that, it’s just drag and drop.

But that’s not me. GUIs, who needs them. Here’s how I do it:

BASH version:

pd ~/Downloads
cp the-file ${DIRSTACK[1]}

DIRSTACK is a shell variable containing an array which is the directory stack. The first element is the current directory which is why we use an index of 1.

In TCSH it’s even simpler, the directory stack is exposed through special variables prefixed with =. =1 is the previous directory, =2 the one beyond that and so on.

pd ~/Downloads
cp the-file =1

We can put these together in to functions/aliases for simplicity of use:

function stack-cp() { cp "$@" ${DIRSTACK[1]} ;}
alias stack-cp 'cp \!* =1'

Giving you a work flow like:

pwd
/deeply/nested/directory
pd ~/Downloads
stack-cp imporant-file.txt

However, you do loose something with this approach. On of the nice features of both BASH and TCSH is that they will Tab-expand variables that are obviously file paths. Say you are in a Rails project and you want to copy in a new image into the assets directory:

pwd
/top/off/a/rails/project
pd ~/Downloads
cp new-image.png ${DIRSTACK[1]}/ap<Hit TAB>

and the command line will magically change to

cp new-image.png /top/off/a/rails/project/app/

Typing out ${DIRSTACK[1]} is a bit much however, so we modify our pd to be a function instead of an alias like so:

function pd() { pushd $@ && l=${DIRSTACK[1]} ;}

We get $l (for last) which can be used it place of ${DIRSTACK[1]}.

popd would then change the stack without updating $l, so that needs to become:

function popd() { builtin popd $@ && l=${DIRSTACK[1]} ;}

(builtin command is the BASH equivalent of calling super.)

You’ll get the same effect in TCSH with the much easier to type =1

pwd
/top/off/a/rails/project
pd ~/Downloads
cp new-image.png =1/ap<Hit TAB>

However, that expands to

cp new-image.png =1/app

which isn’t as obvious.

Yeah, yeah, you’re probably never going to use this the way I do. But, now you know a bit more about what’s happening in your shell and a trick or two when you need them.

Comments