4

I'm trying to customize my bash prompt and I'm having trouble with a few conditionals.

My current PS1 looks like this.

export PS1="\
$PS1USERCOLOR\u\
$COLOR_WHITE@\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [ \$? = 0 ]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\     
$COLOR_WHITE"
  • The first 6 lines just set regular PS1 stuff.

  • Line 7 then calls a function to display the current git branch and status if applicable.

  • Line 8 then tests the return code of the previous command and changes the colour of the $ on the end.

  • Line 9 sets the prompt back to white ready for the user's command.

However line 8 is responding to the return code from line 7's function and not the previous command as I first expected.

I've tried moving line 8 before line 7 and eveything works as it should. But I don't want line 8 before line 7, the $ must be on the end.

I've tried setting a variable earlier on to be the value of $? and then testing that variable like so

export PS1="\
\`RETURN=\$?\`\
$PS1USERCOLOR\u\
$COLOR_WHITE@\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [ \$RETURN = 0 ]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\     
$COLOR_WHITE"

But this doesn't work.

Does anybody have any idea how to solve my problem?

5
  • I'd split all the if statements into a separate function or script, and test independently. return the color as as string Commented Jul 3, 2014 at 20:21
  • You are setting a variable in a sub-shell. That doesn't carry to another sub-shell or the parent shell. Commented Jul 3, 2014 at 20:23
  • That makes sense! Any idea how to do this correctly? Commented Jul 3, 2014 at 20:24
  • 1
    Don't use two sub-shells for the two tests. Use one, set RETURN at beginning of that sub-shell and then use it the end. Commented Jul 3, 2014 at 20:29
  • See also How to get green/red terminals under OpenBSD? and Stateful bash function Commented Jul 3, 2014 at 20:30

3 Answers 3

4

The proper way is to use PROMPT_COMMAND like so:

prompt_cmd () {
    LAST_STATUS=$?
    PS1="$PS1USERCOLOR\u"
    PS1+="$COLOR_WHITE@"
    PS1+="$COLOR_GREEN\h"
    PS1+="$COLOR_WHITE:"
    PS1+="$COLOR_YELLOW\W"
    if type parse_git_branch > /dev/null 2>&1; then
        PS1+=$(parse_git_branch)
    fi
    if [[ $LAST_STATUS = 0 ]]; then
        PS1+="$COLOR_WHITE"
    else
        PS1+="$COLOR_RED"
    fi
    PS1+='\$'
    PS1+="$COLOR_WHITE"
}

Since PROMPT_COMMAND is evaluated prior to each prompt, you simply execute code that sets PS1 they way you like for each prompt instance, rather than trying to embed deferred logic in the string itself.

A couple of notes:

  1. You must save $? in the first line of the code, before the value you want is overwritten.
  2. I use double quotes for most of the steps, except for \$; you could use PS1+="\\\$" if you like.
Sign up to request clarification or add additional context in comments.

1 Comment

That's excellent and far more elegant. I've used it exactly like that, although I've added a space after the $.
2

The standard solution to this problem is to make use of the bash environment variable PROMPT_COMMAND. If you set this variable to the name of a shell function, said function will be executed before each bash prompt is shown. Then, inside said function, you can set up whatever variables you want. Here's how I do almost exactly what you're looking for in my .bashrc:

titlebar_str='\[\e]0;\u@\h: \w\a\]'
time_str='\[\e[0;36m\]\t'
host_str='\[\e[1;32m\]\h'
cwd_str='\[\e[0;33m\]$MYDIR'
git_str='\[\e[1;37m\]`/usr/bin/git branch --no-color 2> /dev/null | /bin/grep -m 1 ^\* | /bin/sed -e "s/\* \(.*\)/ [\1]/"`\[\e[0m\]'
dolr_str='\[\e[0;`[ $lastStatus -eq 0 ] && echo 32 || echo 31`m\]\$ \[\e[0m\]'
export PS1="$titlebar_str$time_str $host_str $cwd_str$git_str$dolr_str"

function prompt_func {
    # Capture the exit status currently in existence so we don't overwrite it with
    # any operations performed here.
    lastStatus=$?

    # ... run some other commands (which will have their own return codes) to set MYDIR
}

export PROMPT_COMMAND=prompt_func

Now bash will run prompt_func before displaying each new prompt. The exit status of the preceding command is captured in lastStatus. Because git_str, dolr_str, etc. are defined with single quotes, the variables (including lastStatus) and commands inside them are then re-evaluated when bash dereferences PS1.

2 Comments

Using PROMPT_COMMAND, you would typically just conditionally set the value of, e.g, dolr_str inside the function, rather than try to embed a command in the value of dolr_str.
True. The above is the result of many edits done in many differing states of mind. :)
0

Solved it!

I need to use the PROMPT_COMMAND variable to set the RETURN variable. PROMPT_COMMAND is a command which is called before PS1 is loaded.

My script now looks like

PROMPT_COMMAND='RETURN=$?'

export PS1="\
$PS1USERCOLOR\u\
$COLOR_WHITE@\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [[ \$RETURN = 0 ]]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\     
$COLOR_WHITE"

1 Comment

You might as well do the full if test in PROMPT_COMMAND and set a variable with the string to use in PS1.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.