Using The Shell Right

Dec 25, 2009

The most powerful part of Unix/Linux/BSD is the command line. In stock trim, the Unix shells are all very effective, but your time can be more effective by customizing the shell. I use bash, so I know that these things work in that shell, but they ought to be easily transferred to others as well.

Aliases

A good friend of mine, Jordan Sissel, once said that if you do something more than once, you’re doing it wrong. His conjecture applies as much to your shell as it does to your browser—computers are great at repetitive tasks, so you shouldn’t bore yourself with such things. Therein lies the most important thing you can do with your shell: make common tasks easier with aliases.

First things first, I use ls a whole lot, and, despite it’s simple makeup, I often mistype the command. I don’t care about being able to easily run Steam Locomotive, and there’s no s or l command, so I replace those all with ls:

alias sl="ls"
alias l="ls"
alias s="ls"
alias ll="ls -l"
alias lh="ls -lh"
alias la="ls -la"
Another thing I do all the time is descend into directories, which means I need to get out of them, too:
alias ..="cd .."
alias ...="cd ../.."
alias ....="cd ../../.."
alias .....="cd ../../../.."
alias ......="cd ../../../../.."
alias .......="cd ../../../../../.."

I always want extended regular expressions, and there are tons of things I don’t want to search when I grep (though I use ack these days):

alias grep="egrep"
alias G="grep -n --color=always --binary-file=without-match --exclude=tags \
--exclude=*-min.js --exclude-dir='.[a-zA-Z]*' --exclude-dir='external' \
--exclude-dir='blib'"

Furthermore, I often want to do recursive greps of a entire codebase, sometimes case insensitive, and like everything else, I mistype it:

alias GR="G -r"
alias RG="GR"
alias GRI="G -r -i"
alias GIR="GRI"
alias IGR="GRI"
alias IRG="GRI"

If you do any sort of system administration, you need to grep the process list; make that easy:

alias paux="ps aux|grep -i"

Is someone shoulder surfing?

alias c="clear"
alias logout="clear; logout"

Matt Behrens tipped me off to this one—type -a tells you a lot more than the standard which:

alias which='type -a'

When I’m writing in a language that requires compilation, I use cowsay to break up the output of each run, so that errors are easy to distinguish between this run and the previous one:

alias gcc='cowsay "Hello"; gcc'
alias g++='cowsay "Hello"; g++'
alias make='cowsay "Hello"; nice -n 10 make'
alias javac='cowsay "Hello"; javac'

Machines that I SSH to often get their names as aliases; I’ve got a bunch more of these:

alias claudius="ssh -Y dinomite@dinomite.net"
alias caligula="ssh -Y dinomite@caligula.dinomite.net"

Prompt

There are numerous articles about pimping out your shell’s prompt, many include previous command exit codes, the time, the current energy of the LHC, and the price of the S&P 500. I have a web browser, so I don’t need all that information—I only put in my prompt things that are pertinent to the task at hand. The things that make up my prompt are a bit complicated, so I build it in stages. First, since I work on a lot of different machines, I always have the hostname in my prompt. To make it easy to tell which machine I’m on, I assign colors to the systems that I use most often:

HOSTNAME=`hostname|sed -e 's/\..*$//'`
if [ $HOSTNAME = 'Caligula' ] || [ $HOSTNAME = 'Caligula.local' ]; then
    export HOST_COLOR="\[\033[1;35m\]"
fi
if [ $HOSTNAME = 'claudius' ]; then
    export HOST_COLOR="\[\033[1;36m\]"
fi
if [ $HOSTNAME = 'dev1' ]; then
    export HOST_COLOR="\[\033[1;34m\]"
fi
if [ $HOSTNAME = 'svr-dev-rw1' ]; then
    export HOST_COLOR="\[\033[1;31m\]"
fi
if [ $HOSTNAME = 'drewfus' ]; then
    export HOST_COLOR="\[\033[1;30m\]"
fi

Next, I capitalize the hostname and make the colon separating it from the path red if I’m currently acting as root via sudo -s. This makes it very hard to forget that the consequences of actions are great at the current time:

COLON_COLOR='0m'
if [ ${UID} -eq 0 ]; then
    COLON_COLOR='1;31m'
fi
if [ ${UID} -eq 0 ]; then
    HOSTNAME="`echo $HOSTNAME|tr '[a-z]' '[A-Z]'`"
fi

Finally, build the actual prompt:

PS1=`echo -ne "$HOST_COLOR$HOSTNAME\[\033[00m\]\[\e[$COLON_COLOR\]:\[\033[33m\]\
w\[\033[00m\]\\[\033[01;33m\]\$\[\033[00m\] "`

What does this look like?
claudius:/usr/local$
And when root:
CLAUDIUS:/usr/local$

History

There are a lot of complicated commands that I only use occasionally; having a big history means if I’ve used it once, I can easily search and find the command later. The histappend options tells bash to append rather than overwrite the history file and cmdhist combines multi-line history commands into a single entry. It’s often useful to run the same command repeatedly, and I find myself typing ls whenever I stop to think; setting HISTCONTROL and HISTIGNORE keeps those actions from filling up my history.

#"I know I've used that command before, but I can't remember the syntax"
export HISTSIZE=50000
shopt -s histappend
shopt -s cmdhist
HISTCONTROL=ignoredups
export HISTIGNORE="&:ls:ll:la:lh:sl"
export HISTTIMEFORMAT='%Y-%m-%d %H:%M:%S - '

Environment Variables

A lot of linuxes come with lackluster program defaults (emacs, more, etc.); you can get better ones by setting environment variables:

export PAGER=/usr/bin/less
export EDITOR='vim -X'
export BROWSER='firefox'
export CC=/usr/bin/gcc

Since we are in the 21st century, I use Unicode:

export LC_ALL="en_US.UTF-8"
export LANGUAGE="en_US.UTF-8"

Functions

I use Perl a lot, and have to deal with keeping modules the same across different systems; this function makes getting the installed version of a module easy:

function perlmodver {
    perl -M$1 -e 'print "Version " . $ARGV[0]->VERSION . " of " . $ARGV[0] . " is installed.\n"' $1
}

The thing I use awk for most often is ’{print $n}’, so I wrote fawk which you give a number and it des just that:

function fawk {
    first="awk '{print "
    last="}'"
    cmd="${first}\$${1}${last}"
    eval $cmd
}

awk also does math:

function calc {
    awk "BEGIN{ print $* }";
}

Tying It All Together

To keep things organized, I separate the above mentioned things into a few different files, so my .bashrc brings them all together. Additionally, I check for a .bash_local file, which isn’t checked into subversion, so that I can have machine-specific alterations to my shell environment.

# .bashrc
source ~/.bash_global
source ~/.bash_aliases
source ~/.bash_functions
if [ -f ~/.bash_local ]
then
    source ~/.bash_local
fi