I'm not a shell expert by any means. I'm certainly comfortable enough on the command line and can pipe I/O fairly effectively; however, I know very little about bash scripting, and irb and svn really cover the vast majority of my Terminal usage aside from the ubiquitous ls and cd.

Even humble cd has features which I had not initially fathomed: it was a wonderful moment when I recently saw Tobi use cd - to flip back and forth from the last directory he had visited. I had been vaguely yearning for a way to go back directories for a while, but never got around to figuring it out. This cd - thing wasn't really what I was looking for, though, because what I wanted was the equivalent of the back and forward buttons in a web browser and cd - doesn't have a memory of anything more than the last directory you were in, via the environment variable OLDPWD.

So I did some googling and found the necessary components to come up with something that is pretty darn satisfying.

How it works

I added three short functions in ~/.bash_profile:

function cd {
    pushd "$@" > /dev/null
}

Instead of its regular behaviour, cd is now mapped to a nifty command called pushd. This command takes a directory path as an argument and changes to that directory, just like cd does, but it also stores the new path in a stack stored in an environment variable.

It outputs the contents of that stack whenever you use pushd, but I don't want to see it every time I change directory which is why I've redirected standard output to oblivion. STDERR is left untouched so if I type a nonexistent directory then I will still find out about it. The "$@" provides pushd with all the arguments I pass to cd separated by spaces, so everything stays the way I typed it; if I were to use "$1" here, then it would choke on directory names with spaces in them.

Now that we are building onto our stack of visited directories every time we cd anywhere, we can use this as a browser history for the shell:

function bd {
    if [ -z $1 ]; then
        n=1
    else
        n=$1
    fi
    pushd +$n > /dev/null
}

function fd {
    if [ -z $1 ]; then
        n=0
    else
        n=$[$1-1]
    fi
    pushd -$n > /dev/null
}

bd is for "back a directory" and fd is for "forward a directory". We are still just using pushd to manipulate the stack, but now we are using a different kind of argument for it. pushd +n will rotate the stack backwards n steps and change to that directory. We give n a default value of 1 when no arguments are passed to bd, and do pushd +n.

For fd, we rotate the other way. The default for n is now 0 and we decrement the argument by 1 otherwise because pushd -0 refers to the bottom element of the stack. If we go forward before going backward, it doesn't care and will just rotate back to the first pushed directory (which is probably ~) and continue rotating from there, which is fine by me.

How it plays

So now I can go back and forward with bd and fd on their own, or do bd 8 to go back wherever I was eight directories ago and then maybe a little fd 3 to revise my search.

Each shell gets its own history which just accumulates and accumulates. If I feel like keeping a Terminal window open for a few years then I can do dirs -c to clear the stack. The way I have this set up, cd without any arguments will swap back and forth between the current and last directory as cd - would normally do, because that's how pushd behaves when you type it by itself. Normally cd by itself would be the equivalent of cd ~, but I never used cd like that in the first place so I'm not missing anything.

There's obviously room to modify this formula and use this wonderful directory stack in different ways. Here is a straightforward reference for the directory stack commands in bash. There's also a few tilde expansions that reference the directory stack which some people might find useful.

Sorry, comments are closed for this article.