Jun 15

Quick Directory Changing in Linux

When working from a terminal, I often find myself typing the same commands over and over. There are a handful of long "cd" commands that I find myself typing regularly. I've found a few ways of speeding up my workflow by using some bash tools and tricks, and now I can usually get where I need to be with just a few keystrokes.

Aliases

Here are a few commands I find myself typing regularly:

cd /usr/lib/python2.6/site-packages
cd /usr/local/src/django
cd ~/projects

One way to speed up your workflow is to alias these commands by adding the following lines to your ~/.bashrc:

alias .sp="cd /usr/lib/python2.6/site-packages"
alias .dj="cd /usr/local/src/django"
alias .p="cd ~/projects"

After restarting bash, you can enter your site-packages folder just by typing ".sp"

Bash Tricks

This can also be done with a function in bash, by adding the following to your ~/.bashrc:

function goto {
    case $1 in
        sp) cd /usr/lib/python2.6/site-packages ;;
        dj) cd /usr/local/src/django ;;
        p) cd ~/projects ;;
    esac
}

This gives you a command called goto, so you can enter your site-packages folder by typing "goto sp"

For more flexibility in your "goto" script, it may be worth it to write it in a more powerful language, such as Python. In this example, we are embedding python code into a bash function:

function goto { $(python <<< "
alias = '$1'
targets = {
    'p': '/home/cody/projects/',
    'dj': '/usr/local/src/django/',
}
try:
    # anything we print gets executed in the current bash environment
    print('cd %s' % targets[alias])
except KeyError:
    print('echo Target not found')
"); }

Because we are working in Python now, we can easily modify our script to be database-backed or use a simple configuration file to store "targets" (aliased directories). Of course, with greater complexity, it would be cleaner to move the python code to a separate file and strip down the bash function to just:

function goto { $(python /home/cody/bin/goto.py $*); }

A Different Solution with Python

This approach works, but is obviously hacky due to the fact that anything sent to stdout in python gets executed in bash. This makes it difficult to use something like optparse to provide arguments and help. Ideally, we could write something in pure python without having to source a bash script to change the working directory, but I have not found a way to do so. You can, however, use exit codes from python to instruct bash on whether to execute or print the output of the python script. This bash script demonstrates the use of exit codes (Note: this also must be sourced with ".", "source" or by placing the source into ~/.bashrc):

function goto {
    TEMPFILE=$(mktemp)
    python /home/cody/bin/goto.py $* > $TEMPFILE
    EXIT_CODE=$?
    if [ $EXIT_CODE == 10 ]; then
        . $TEMPFILE
    else
        cat $TEMPFILE
    fi
}

Now we need to create our python file (goto.py). Note the use of "exit(10)." This is the exit code that gets picked up in our bash script as $?. Here we will use optparse to provide command line argument parsing. Now we will have a help menu (accessible with --help or -h) and a list-display of the available aliases (accessible with -l or --list):

targets = {
    'p': '/home/cody/projects/',
    'dj': '/usr/local/src/django/',
}

import sys
from optparse import OptionParser

def execute_and_exit(command):
    print(command)
    # bash script uses return code as flag to execute command
    exit(10)

parser = OptionParser()
parser.add_option('-l', '--list',
    action='store_true', dest='display_list', default=False,
    help='Display list of targets.')
(options, args) = parser.parse_args()

if options.display_list:
    for name, path in targets.iteritems():
        print('%s: %s%s' % (
            name,
            ''.join(' ' for i in range(5 - len(name))),
            path
        ))
else:
    alias = args[0]
    try:
        execute_and_exit('cd %s' % targets[alias])
    except KeyError:
        print('Target not found')

After moving the bash to a file called ~/bin/goto.sh and the python to a file called ~/bin/goto.py, we have a pretty useful alias program. Now we can do the following:

cody@laptop:~$ source ~/bin/goto.sh #alternatively add this line to your ~/.bashrc
cody@laptop:~$ goto -h
Usage: goto.py [options]

Options:
  -h, --help  show this help message and exit
  -l, --list  Display list of targets.
cody@laptop:~$ goto -l
p:     /home/cody/projects/
dj:    /usr/local/src/django/
cody@laptop:~$ goto dj
cody@laptop:/usr/local/src/django$ goto p
cody@laptop:~/projects$

Other Things from Bash

There are also a few shortcuts in bash to re-enter directories that you have been in since you started your bash session. For example, if you are in your projects directory and you need to change to /tmp to do a quick task, you may re-enter your projects directory using:

cd -

This command always takes you back to the last directory you were in.

There are also the little-known *nix commands pushd and popd. These commands build a directory stack, as illustrated by this example:

cody@laptop:/$ pushd .
/ /
cody@laptop:/$ cd ~
cody@laptop:~$ pushd .
~ ~ /
cody@laptop:~$ cd /tmp
cody@laptop:/tmp$ pushd .
/tmp /tmp ~ /
cody@laptop:/tmp$ popd
/tmp ~ /
cody@laptop:/tmp$ popd
~ /
cody@laptop:~$ popd
/
cody@laptop:/$ popd
bash: popd: directory stack empty
cody@laptop:/$

In this example, I move from /, to ~, to /tmp, and use popd to go back to each directory in reverse order.

History

In addition, Bash history provides a way to quickly run any command you've run earlier in your session by typing CTRL-R followed by a few unique letters from the command you've typed before. If I had ran the command "cd /usr/lib/python2.6/site-packages" several minutes ago, and I need to get back to my site-packages folder, I can type "site" and it will likely find the "cd" command in the Bash history (unless I had more recently typed a command with the word "site" in it, in which you can hit CTRL-R again to match commands further back in the history).

Another bash trick that is very time-saving is to use ALT+. to repeat the last word of the last command typed. For example, say I'm looking for a file, so I run:

ls ~/projects/python/codysoyland.com

After I see the file I'm looking for, now I want to cd to the directory and start working, so I just type "cd " and hit ALT+. to insert the path I typed in the last command.