Bash for Fun & Profit

A few enhancements can improve your experience working in a shell.

I like the command line.

I enjoy the control that using a shell gives me over my daily work environment.
And I like that I can do most things without needing my mouse. The less often I
remove my hands from my keyboard, the faster my work goes.

But there are a few things I miss in a standard command-line environment.

What was that name, again?

For example, bash offers tab completion. But that doesn’t extend to
interactions with git. Wouldn’t it be nice if I could tab-complete git
commands or the names of my remotes and branches?

Well, I can.

The folks who create such things have been kind enough to provide a shell
script that sets this up. And it’s not hard to install.

The script is
called git-completion and it’s available in bash, tcsh and zsh flavors.

To use it, download the version of the script that corresponds to your
preferred shell from the tag of the git repo that corresponds to the version of
git you are using. I’ve got git 1.8.4.2 installed on my machine, so
this is the version for me.
I put it in my home directory:

Where am I, what am I doing?

I work on contract. I’ve got a lot of projects ongoing over time. Keeping
track of all the various repositories, branches, tags and so on can get a bit
confusing. What’s a poor developer to do?

Enter
git-prompt.
Again, I place this code in my home directory, and then source it from my
.profile:

source ~/.git-prompt.sh

Once I do this, I can use the __git_ps1 shell command and a number of shell
variables, to configure PS1 and change my shell prompt. I can show the name
of the current branch of a repository when I’m working in one. I can get
information about the status of HEAD, modified files, stashes, untracked files
and more.

There’s two ways to do this. The first is to use __git_ps1 as a command
directly in a PS1 expression:

PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '

The result looks like this:

That’s not bad, but a bit of color would be nice, and perhaps breaking things
onto more than one line so I can parse what I’m seeing more easily would be
helpful.

For that, I need to change strategies. The __git_ps1 command can be used as
a single element in the expression for PS!. But it can also be used itself
as the PROMPT_COMMAND env variable. This variable names a command that will
be used to form PS1 dynamically.

When I use __git_ps1 in this way, a couple of things happen. First, instead
of taking only one optional argument (a format string), I can provide two or
three arguments. The first will be prepended to the output of the command. The
second will be appended after. Both of these arguments are required, but can be
empty strings. The optional third argument will be used as a format string for
the output of the command itself. If there is no output, it will not be used at
all.

When combined these three elements can be very expressive. For example, My
normal OS X command prompt can be expressed like so: \h:\W \u\\\$. If I use
this as the second argument, leave the first empty and provide a simple format
ending in a newline for the __git_ps1 output, my .profile looks like this:

PROMPT_COMMAND='__git_ps1 "" "\h:\W \u\\\$ " "[%s]\n"'

That produces a nice two-line prompt that appears when I’m in a git repo, and
disappears when I’m not:

Setting a few env variables in .profile expand this further, colorizing the
output and providing information about the state of my repo:

But wait, there’s more.

The problem with this is that it doesn’t play well with another of my favorite
Python tools, virtualenv. When I activate a
virtualenv, it prepends the name of the environment I am working on to my
command prompt. But it uses the standard PS1 shell variable to do this.
Since I’ve used the PROMPT_COMMAND to set my prompt, PS1 doesn’t get
used, and this nice feature of virtualenv is lost.

Luckily, there is a way out. Bash shell scripting offers
parameter expansion
and a trick of the syntax for that can help me. Normally, a shell parameter is
referenced like so:

$ PARAM='foobar'
$ echo $PARAM
foobar

In complicated situations, I can wrap the name of the paramter in curly
braces to avoid confusion with following characters:

$ echo ${PARAM}andthennotparam
foobarandthennotparam

What is not as well known is that this curly-brace syntax has a lot of
interesting variations. For example, I can use PARAM as a test and
actually print something else entirely:

$ echo ${PARAM:+'foo'}
foo
$ echo ${PARAM:+'bar'}
$

The key here is the :<char> bit immediately after PARAM. If the +
char is present, then if PARAM is unset or null, what comes after is not
printed, otherwise it is.

When I was looking at the script that
activates a virtualenv in bash
I noticed that it exports VIRTUAL_ENV. This means that so long as a
virtualenv is active, this paramter will be set. And it will be unset when no
environment is active.

I can use that!

Armed with this knowledge, I can construct a shell expression that will either
print the name of the active virtualenv in square brackets, or print nothing if
no virtualenv was active:

And voilà! I’ve got a shell prompt that keeps me informed about all the things
I need to know when working on a daily basis:

Wrap-up

Given the amount of time I spend at the command line, it only makes sense to
work a bit at customizing it. The command prompt is something I see all day
every day. Why not use it to my advantage? Combined with other minor
modifications like git-completion, my shell becomes a power tool. There’s still
a lot to learn, but I’m
pretty happy with this so far.