5

I'm new to Bash programming, and I'm working on creating a custom Bash command prompt. My goal is to create a prompt which only shows the login name and host name when they differ from what I normally use. I'm also looking to append the current Git branch to the command prompt when in a directory which is under Git version control.

I would like to color the login and host name section green, the directory path blue, the Git branch section pink, and separators (: and $ characters) white. However, when the previously executed command returns anything other than zero, I would like to color the $ separator red. The general format without colors looks like this:

loginname@hostname:~/current/path:branchname$

The only sections that are mandatory are the directory path and the $ character. Here's the code I've written for my .bashrc file:

MYNAME="gwen"
MYHOST="gwen-laptop"

RED="\[\033[31m\]"
WHITE="\[\033[0m\]"
GREEN="\[\033[01;32m\]"
BLUE="\[\033[01;34m\]"
PINK="\[\033[01;35m\]"

DOLLAR="if [ \$? = 0 ]; then echo ${WHITE}\$; else echo ${RED}\$${NORMAL}; fi"
GITBRN='$(__git_ps1 "\033[0m:\033[01;35m%s")'

USERNM="if [ \u != ${MYNAME} ]; then whoami; fi;"
HOSTNM="if [ \h != ${MYHOST} ]; then echo -n @; hostname; fi;"
COLONM="if [ \u != ${MYNAME} ] || [ \h != ${MYHOST} ]; then echo -n :; fi;"

PS1="${GREEN}\`${USERNM}\`\`${HOSTNM}\`${WHITE}\`${COLONM}\`${BLUE}\w${GITBRN}\`${DOLLAR}\` "

This code meets all of my requirements, except that it leaves the $ character white at all times, and does not color it red at the appropriate times. (I suspect the problem is that the "\$?" in DOLLAR references the previously executed command, but DOLLAR is executed last when constructing PS1, so the previously execute statement is no longer the command which was run before PS1 construction began; it's something which was executed in order to create the command prompt.) I'm not sure how to solve this problem.

This code is ugly and needs to be refactored. I was trying to move all the color codes into their own variables, but when I used these color variables in the code for GITBRN, thing went haywire, so I ended up using literal colors there instead.

I've spent an entire day trying to get this code working, and I think I'm going nowhere at this point. Any suggestions for how to get that dollar sign colored red at the appropriate time would be most appreciated. I'm also open to suggestion on refactoring the code to make it cleaner and more readable.

P.S. I'm a Ubuntu Linux (Lucid Lynx) user.

Gwen Avery
  • 105
  • 1
  • 4
  • Unrelated to your problem at hand: I believe it would be sufficient to do `if (hostnamecondition); then HOSTNM=''; else HOSTNM="@$(hostname)";fi` or something like that. I don't think the hostname would usually change: when you ssh to another machine, you spawn a new bash there, which would run your code again. Not checking for the hostname for every time you show a prompt will have a slight performance benefit. – Ulrich Schwarz Aug 21 '11 at 08:41
  • The problem here is that your prompt definition contains executed commands, which will set `$?` after running. You just need to copy it into a custom variable, although I can't quite work out the scoping issues, so the answers below remain the best solution for now. – IMSoP Jun 20 '13 at 15:10

3 Answers3

5

Use the PROMPT_COMMAND variable, which is executed before each primary prompt according to the bash man page.

For example (this doesn't work yet, I'm trying to get it working right, but I think it's possible):

PROMPT_COMMAND="if [ \$? = 0 ]; then DOLLAR="${WHITE}\$${NORMAL}"; else DOLLAR="${RED}\$${NORMAL}"; fi"

Edit: due to frustrations with executing commands and nonprinting characters inside PS1 (the \[ and \] sometimes get printed out literally instead of used as hints to PS1), I've come up with this (replace ... with whatever you want in your prompt):

PROMPT_COMMAND='if [ $? = 0 ]; then DOLLAR_COLOR="\033[0m"; else DOLLAR_COLOR="\033[31m"; fi'
PS1='...\[$(echo -ne $DOLLAR_COLOR)\]$\[\033[m\] '

Of course, using $() you could put whichever parts of this you like inside PS1 instead of using PROMPT_COMMAND, I just like it this way so that PROMPT_COMMAND contains the logic and PS1 contains the display commands.

jtbandes
  • 115,675
  • 35
  • 233
  • 266
3

I got it to work:

PROMPT_COMMAND='if [ $? = 0 ]; then PS1="\[\e[32;1m\]\u@\[\e[0m\e[30;47m\]\H\[\e[0m\]:\[\e[34;1m\]\w\[\e[0m\]$ "; else PS1="\[\e[31;1m\]\u@\[\e[0m\e[31;47m\]\H\[\e[0m\]:\[\e[31;1m\]\w\[\e[0m\]$ "; fi'

enter image description here

Standing on the sholders of @jtbandes. @jtbandes is the original author of the idea.

Aleksandr Levchuk
  • 3,751
  • 4
  • 35
  • 47
  • 1
    Do you think it would be possible to display the red color **only** when a command fail and not when you just push enter on an empty command line? – Stéphane Feb 06 '12 at 09:38
2

Here is Alexsandr's answer modified to display the red color only when a command fails and not when you push enter on an empty command line, as Stéphane requested.

trap 'PREVIOUS_COMMAND=$THIS_COMMAND; THIS_COMMAND=$BASH_COMMAND' DEBUG
read -r -d '' PROMPT_COMMAND << 'END'
    if [ $? = 0 -o $? == 130 -o "$PREVIOUS_COMMAND" = ": noop" ]; then
        PS1='\[\e[32;1m\]\u@\[\e[0m\e[30;47m\]\H\[\e[0m\]:\[\e[34;1m\]\w\[\e[0m\]$ '
    else
        PS1='\[\e[31;1m\]\u@\[\e[0m\e[31;47m\]\H\[\e[0m\]:\[\e[31;1m\]\w\[\e[0m\]$ '
    fi
    : noop
END
Community
  • 1
  • 1
dtolnay
  • 9,621
  • 5
  • 41
  • 62