Go and Save — Productivity Tools

I found a timesaving way of navigating my way around directory trees. I discovered this a few decades ago, and despite mentioning it to lots of people, it's still not generally used. But it's way better than the pushdir and popdir that seems popular.

My commands are called:

go
Takes you to a named directory
save
Saves the current directory to a symbolic name
wmi
Finds which named directory you are in

Here's a sample session:

# cd /some/long/and/hard/to/get/right/pathname
# save shellfish
# cd /another/really/ugly/pathname
# save starfish
# cd /yet/another/horrible/pathname
# save urchin
 
...
(some time later)
...
 
# go shellfish
/some/long/and/hard/to/get/right/pathname
# go urchin
/yet/another/horrible/pathname
# go starfish/subdir
/another/really/ugly/pathname/subdir

Implementation

The trick is that every time you save a directory path, it creates an entry in ~/.go-dirs with the mnemonic name. So, when we did save shellfish, we created a file called ~/go-dirs/shellfish whose contents are the long and hard to remember pathname /some/long/and/hard/to/get/right/pathname.

When we issue the go command, we're just getting the pathname from a file in the ~/.go-dirs directory, and setting the current directory to its contents.

Here are the bash implementations, just put these into your .bashrc and create a ~/go-dirs directory, and you're off to the races!

function go {
    if [[ $1 == */* ]]
    then
        cd `cat ~/.go-dirs/${1%%/*}`/${1#*/}
        pwd
    else
        cd `cat ~/.go-dirs/$1`
        pwd
    fi
}
 
function save {
    pwd >~/.go-dirs/$1
}
 
function wmi {
    grep `pwd` ~/.go-dirs/*
}

Basically, the magic in go is that it looks to see if the string contains a slash delimiter (the if [[ $1 == */* ]] part). If there is a slash, then it means that the subdirectory following the mnemonic name should be appended to the expansion of the mnemonic. In which case, there's some more bash magic with the %% operator (delete longest match of substring from back of string) and the # operator (delete shortest match of substring from front of string).

Paradigm Shift

Usage of go/save is not limited to the plain “I am here” and “put me there” paradigm as outlined above. We can extend the subdirectory concept by adding symbolic links. Here I'm referring to a more “idiomatic” view of the directory structure.

For example:

# go accounting/fy14

In normal interpretation, this would mean, literally, “go to the directory called “accounting” and then change directory relative to that, to the fy14 subdirectory.”

But by mapping the literal meaning, on a one-to-one basis with the idiomatic meaning, we can interpret this as “take me to the FY14 accounting directory.” This is a different interpretation on the same command.

It can also be accomplished by use of a node and links. The node is the “noun” and thus “idiomatic locus” of the concept — in the example above, it's the “accounting” token. We don't care where it lives in the on-disk hierarchy; this locus is the root of the paradigm.

The idiomatic target, or “adjective,” in this case “fy14” is the actual directory or a symbolic link to the actual directory, representing the adjective associated with the noun. In this case, the noun is “accounting” and the adjective is “fy14.”

Extending the paradigm to bash

The current implementation makes use of two functions, go and save, which are not “first class citizens.” What I mean by that is that they are helper functions, but the concept that they embody isn't available on a generalized basis. So, there is a third command called where which is implemented as follows:

# usage:  where [/additional/paths]
#   same as "go" except just prints resulting location instead
#   of cd'ing to it.  Useful for backtick expressions, e.g. cat `where go`/go
function where {
    if [[ $1 == */* ]]
    then
        echo `cat ~/.go-dirs/${1%%/*}`/${1#*/}
    else
        echo `cat ~/.go-dirs/$1`
    fi
}

This is used to resolve the mnemonic path to an absolute path:

cat `where accounting/fy14`/profits.text

This invokes “where accounting/fy14,” which prints the value to standard output, and the backtic shell expansion substitutes that on the command line of cat.

It's a little ugly, typing-wise!

It would be great to have a notation that works just like the tilde notation (e.g., “~rk/spud.txt” means a file called spud.txt in the home directory of the user called rk.) To that end, I propose a new notation: “+accounting/fy14.” The “+” expands to an alias in the user's alias directory, and if not found, an alias within the system wide alias directory.

For example:

cd +accounting/fy14

would replace the command “go accounting/fy14.” Or indeed:

cat +accounting/fy14/profits.txt

would elegantly look in the user's .go-dirs directory, find the accounting alias file, expand it to the actual pathname of “accounting,” add fy14/profits.txt to it, and, via cat, print out the contents.

For system use, the system could provide default values in e.g. /etc/go-dirs, and if the user didn't have an accounting entry, would use that. In case both existed, the user's accounting value would be used first, unless the double plus “++” was used, in which case the user's version would be ignored.

Anyone care to slip that into bash for me? :-)

Update

A coworker (Ben Gardiner) suggested a novel workaround that abuses /etc/passwd. I integrated his suggestion, and got the syntax down to the following (note that “+” doesn't work in /etc/passwd so I used “@”):

cat ~@/accounting/fy14/profits.txt

to use the “system wide” version, and:

cat ~/@/accounting/fy14/profits.txt

to use the “user-local” version.

The only other change from the description above is that the accounting entity is now a symbolic link rather than a file containing a pathname.

Thanks Ben!