Whenever I'm on a box that I manage, I tend to succumb to a nervous habit of checking the load average of the system frequently with `w` or `uptime`. A few years ago, getting tired of having to keep an eye on it that way, I pondered the possibility of having the current load average visible in my shell prompt. It would be even better, I thought, if the prompt would also change color to reflect the level of load. In fact, being able to stick dynamic data of any kind into the prompt would open the door to a myriad of ideas, but how does one go about it? I had always been aware of the substitution escape sequences available in most shells including standard prompt elements like \u for user, \h for hostname, etc. But what I wanted to know was can you get arbitrary data, the output of programs or scripts, into the prompt? It turns out you can, and it's not very difficult, at least with GNU bash.

If you look at the man page, the following shell variable is listed,

... PROMPT_COMMAND If set, the value is executed as a command prior to issuing each primary prompt. ...

As stated, when this variable is set, the shell will execute the command it contains prior to each output of the prompt. With it, we can reset the contents of the prompt each time it is displayed, like so,

PROMPT_COMMAND='PS1="`date` "'

In this example, just before the prompt is displayed, the value of PS1 (bash's primary prompt format variable) will be set to the output of the date command, followed by a space. It looks like this,

Thu Oct 19 15:30:27 EDT 2006 uname -m x86_64 Thu Oct 19 15:30:29 EDT 2006

See how the time changes? I admit that this example is not incredibly useful or attractive, but it suffices as a simple illustration. The point is that by manipulating PS1 in PROMPT_COMMAND, you can insert the output of any program or bash function into your prompt, each time the prompt is redisplayed.

Armed with this knowledge, it was pretty easy to get the load average, nicely colored, into the prompt. Consider the following ruby script,

#!/usr/bin/ruby # uptime.rb load_avg = IO.read("/proc/loadavg", 4).to_f mail = Dir.entries("/home/cody/Maildir/new").length <= 2 ? "" : "+" if load_avg <= 0.05 printf "%0.2f%s", load_avg, mail elsif load_avg <= 0.25 printf "\\[\033[32m\\]%0.2f%s\\[\033[00m\\]", load_avg, mail elsif load_avg <= 0.50 printf "\\[\033[33m\\]%0.2f%s\\[\033[00m\\]", load_avg, mail else printf "\\[\033[31m\\]%0.2f%s\\[\033[00m\\]", load_avg, mail end

The script reads the first four bytes of /proc/loadavg, which should contain the load average for the past sixty seconds, and prints it to stdout wrapped in shell escape sequences that color the value based on how high it is. Load averages will be displayed in red if above 50%, orange if above 25% but below 50%, and so on. As an added bonus, it also checks my maildir and, if I've got new mail, adds an indicating character to the prompt. Below is the required PROMPT_COMMAND and a few examples of what the prompt looks like.

PROMPT_COMMAND='PS1="\u@\h(`~/bin/uptime.rb`)% "' cody@gypsy(0.02)% cody@gypsy( 0.77 )% cody@gypsy( 0.21+ )%

One last tweak I find useful is to display the number of currently backgrounded jobs, as it prevents me from backgrounding emacs or other interactive programs and then forgetting about them. bash includes the '\j' escape for this purpose, but I'd only like to see the number in my prompt if there's at least one backgrounded job. It's simple enough to whip up a bash function to get us there. Here's the script, the updated PROMPT_COMMAND and the new prompt in action,

get_jobs() { num_jobs=`jobs | wc -l` if [ $num_jobs -ne "1" ] then let "num_jobs -= 1" echo -n "/${num_jobs}" fi } PROMPT_COMMAND='PS1="\u@\h(`~/bin/uptime; get_jobs`)% "' cody@gypsy( 0.09 )% cat & [1] 12165 [1]+ Stopped cat cody@gypsy( 0.08/1 )% cat & [2] 12171 [2]+ Stopped cat cody@gypsy( 0.08/2 )%

I've found this technique to be very handy. While there is certainly a limit to the amount of junk you can cram into your prompt before it becomes unwieldy, I think the combination of user, host, load average, mail, and jobs results in something that is pretty useful but still pleasant to look at.