Yet Another Neat $PS1 Prompt for Bash

(With  google returning more than a million hits for the search “bash PS1”, it may be a little presumptive that I have anything to add to the conversation, but this was an interesting exercise for me, so I’ll share it anyways.  Besides this may actually be my most nitpicky *nix nerd post yet.) Here’s my brand new … super-duper space-saving, non-forking, color-changing PS1 prompt (that doesn’t mess up readline!)
export PS1='[\[\e[$(((($?>0))*31))m\]\h:\W\[\e[00m\]]\$ '
I happened across a blog post (8 Useful and Interesting Bash Prompts) covering different PS1 prompts that people are using.  I hadn’t thought of it before, but the first prompt ”Show Happy face upon successful execution“ caught my attention because I write a lot of scripts, and of those many are used by other programs–so return codes are important, and not something I always remember to check.  I hadn’t thought of it before, but I liked the idea! Anyways, it got me thinking about how it works … a lot of the fancy PS1 formulas you see out there are running several commands (even if it isn’t obvious–generally if a bracket or tick is involved, there’s a subshell forking out.)  When your system is grinding to a halt, every fork counts.  So I don’t like the idea of running an additional shell script every time I press enter. So, the example that Joshua Price provides, while really cool, adds extra forks every time you hit the return key.  This is pretty easy to demonstrate using bash’s “set -x” command:

Each one of those ”++” signs you see is a new process being created.  I’m a bit of an efficiency freak, so I don’t like that. Curiosity got the better of me, and I somehow decided that I would achieve the same end (return status indication within PS1) without a single forked process.  So I went about modifying my (now previously) favorite PS1 prompt … the ultra-simple:
PS1='[\h:\W]\$ '
I like it because it tells me what host I’m on, the short directory name, and whether or not I’m root.  I like compact, and this is nice because I wrap lines less often.  That’s what I want it to look like when I’m done (smileys are a cool novelty, but it’s extraneous, I only want useful information in a compact form.)  So, this is what I came up with …
PS1='[\[\e[$(((($?>0))*31))m\]\h:\W\[\e[00m\]]\$ '
And before I dissect what it does, let’s compare “forkiness” … (and see how it looks too.)

Super-duper space-saving, non-forking, color-changing PS1 (that doesn't mess up readline!)

Ahhh, that’s better.  It actually adds useful information without additional clutter.  It’s unobtrusive and only becomes obvious when there is new information that needs my attention.  Oh, and I almost forgot, no child processes. The mechanics of the non-forking prompt: There’s a lot going on in that short command, so let me cover some of the pieces.  The really interesting parts are all achieved using bash’s math functions.  At the core of all this is “(($?>0))” which returns a boolean value if the return code of the program’s exit was greater than zero (the double parens tell bash it’s going to do math.)  Using the boolean return is actually important, because it is always either 0 or 1, and exit codes can be 0-255.  We then multiply the evaluation of the return code by thirty-one which is the value to specify a red foreground in the escape sequence (I won’t cover that part here, terminal color sequences are covered extensively elsewhere.)  Then wrap it all in another set of double-parens, and prepend a $ that tells bash it should reference the return value inline and expand it just like a variable–these are all things bash does without forking! It’s easier for me to understand when I see it work, so I’ll demonstrate (color added to highlight grouping):
[tag:~]$ true; echo $(( (( $? > 0 )) * 31 ))
[tag:~]$ false; echo $(( (( $? > 0 )) * 31 ))
It’d be easy to do green and red, or other color combinations too, it’s just basic math at that point. The rest is pretty simple.  Where you see \[ or \] those are actually escape sequences for readline!  Readline counts the number of characters present and uses that to determine where to place the cursor; ANSI escape sequences get counted too, and the escapes specify that the enclosed section shouldn’t be counted.  Without those escapes, long lines get weird when you edit them, text doesn’t get overwritten in the right places, and your cursor appears in the wrong place with no obvious way to position it correctly.  In other words, things get a little weird. Then the colors … \e[31m will change the foreground color to red, and \e[0m will return it to the terminal’s default.  So near the end of the prompt, we always reset back even if not needed.