Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.
GREEN="\e[1;32m"
RED="\e[1;31m"
NONE="\e[m"

get_exit_status(){
   es=$?
   if [ $es -eq 0 ]
   then
       echo -e "${GREEN}${es}${NONE}"
   else
       echo -e "${RED}${es}${NONE}"
   fi
}

get_path(){
    #dummy function
    echo "PATH"
}

PROMPT_COMMAND='exitStatus=$(get_exit_status)'

The following gives me the correct exitStatus but colour variables are not expanded:

PS1='${RED}\h $(get_path) ${exitStatus}${NONE} '

However, the one below, gives me the colours but the exit status does not update:

PS1="${RED}\h $(get_path) ${exitStatus}${NONE} "

What is the right way to do this? How can I fix this so that the exitStatus and colours both work?

share|improve this question
add comment

5 Answers

up vote 4 down vote accepted

Gilles identified your main problem, but I wanted to try explaining it differently.

Bash is effectively calling echo on your PS1, not echo -e.

So it's like you're doing:

echo '\e[1;32m\h\e[m'

If you try running that, you will see it doesn't work.

But bash gives you a way to write special characters that doesn't require using echo -e. It looks like $'\octal number'.

The special character in all the escape sequences is \e, which just means Escape. Escape's octal value is \033.

So we want it to expand to this instead:

echo $'\033[1;31m'"${HOSTNAME}"$'\033[m'

To do this, you can change your definition of GREEN, RED, and NONE, so their value is the actual escape sequence.

GREEN=$'\033[1;32m'
RED=$'\033[1;31m'
NONE=$'\033[m'

If you do that, your first PS1 with the single quotes should work:

PS1='${RED}\h $(get_path) ${exitStatus}${NONE} '

However, then you will have a second problem.

Try running that, then press Up Arrow, then Home, and your cursor will not go back to the start of the line.

To fix that, change PS1 to include \[ and \] around the color escape sequences, e.g.

PS1='\[${RED}\]\h $(get_path) \[${exitStatus}\]\[${NONE}\] '

And it should all be working.

(I'm not sure why putting \[ around ${exitStatus} works, because the exit status number shouldn't have those around it, but it seems to work for me.)

share|improve this answer
add comment

When you run PS1='${RED}\h $(get_path) ${exitStatus}${NONE} ', the PS1 variable is set to ${RED}\h $(get_path) ${exitStatus}${NONE}, where only \h is a prompt escape sequence. After the prompt sequences are expanded (yielding ${RED}darkstar $(get_path) ${exitStatus}${NONE}), the shell performs the usual expansions such as variable expansions. You get a displayed prompt that looks like \e[1;31mdarkstar PATH 0\e[m. Nothing along the way expands the \e sequences to actual escape characters.

When you run PS1="${RED}\h $(get_path) ${exitStatus}${NONE} ", the PS1 variable is set to \e[1;31m\h PATH 0\e[m. The variables RED, exitStatus and NONE are expanded at the time of the assignment. Then the prompt contains three prompt escape sequences (\e, \h, and \e again). There are no shell variables to expand at this stage.

In order to see colors, you need the color variables to contain actual escape characters. You can do it this way:

RED=$'\033[1;31m'
NONE=$'\033[m'
PS1='\[${RED}\]\h \w $?\[${NONE}\] '

$'…' expands backslash-octal sequences and some backslash-letter sequences such as \n, but not including \e. I made three other changes to your prompt:

  • Use \[…\] around non-printing sequences such as color-changing commands. Otherwise your display will end up garbled because bash can't figure out the width of the prompt.
  • \w is a built-in escape sequence to print the current directory.
  • You don't need anything complicated to show $? in the prompt if you don't have a PROMPT_COMMAND in the first place.
share|improve this answer
    
I think the idea was to make the prompt be green on success and red on failure. –  mattdm Mar 3 '11 at 1:20
    
Yes, PS1 is wrong, but the advice to use $'...' for RED and GREEN should make it work using dogbane's PS1. –  Mikel Mar 3 '11 at 2:05
add comment

Here you go - This Works For Me (TM) in Ubuntu and other Linuxes (Linuxen?).

The reason for putting the exit code detection in $PS1 is that one host has a read-only $PROMPT_COMMAND set before .bashrc is read.

share|improve this answer
add comment

Try:

PS1='`exitStatus=$?;if [ $exitStatus -eq 0 ];then echo "\['${GREEN}'\]";else echo "\['${RED}'\]";fi;echo "\h $(get_path) ${exitStatus}${NONE}"`'
share|improve this answer
1  
Thanks, this works, but is there any way to accomplish this without having to embed an if-statement within the prompt? –  dogbane Mar 2 '11 at 8:34
add comment

Here is the approach I have gone with, it avoids the use of PROMPT_COMMAND.

# This function is called from a subshell in $PS1,
# to provide a colourised visual indicator of the exit status of the last run command
__COLOURISE_EXIT_STATUS() {
    # uncomment the next line for exit code output after each command, useful for debugging and testing
    #printf -- "\nexit code: $1\n" >&2
    [[ 0 == "$1" || 130 == "$1" ]] && printf -- "$GREEN" || printf -- "$RED"
}

Then my $PS1 is as follows:

PS1='# ${debian_chroot:+($debian_chroot)}'"${GREEN}\u${YELLOW}@${DARK_YELLOW}\h${WHITE}:${LIGHT_BLUE}\w${WHITE}\n"'\[$(__COLOURISE_EXIT_STATUS $?)\]# \$'"\[${WHITE}\] "
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.