6

I'm trying to set up a bash prompt for - hostname(screen#):directory$ which is coloured green if the last command completed successfully, red if not. This is what I have so far, which actually does the job but seems to cause display problems if the command wraps a line:

 PS1="\[\`if [[ \$? = "0" ]]; then echo '\e[32m'; else echo '\e[31m' ; fi\` - \h(${WINDOW}):\W$\e[00m "

Googling the issue I found this helpful SO post with a comment that mentions wrapping nonprinting characters in \\[ and \\] to avoid this issue. Therefore I tried the following, but it did not solve the issue, and furthermore breaks the colour change:

PS1="\[\`if [[ \$? = "0" ]]; then echo '\e[32m'; else echo '\e[31m' ; fi\`\] - \h(${WINDOW}):\W$\[\e[00m\] "

How can I keep the structure of this prompt, with colours, but fix it so that long commands are displayed properly?

dotancohen
  • 15,864

4 Answers4

9

I have a fancy prompt with colors, and now bash doesn't seem to know how wide my terminal is. Lines wrap around incorrectly.


I have another proper way to do this, put this code in your ~/.bashrc or create a new file and source file :

PROMPT_COMMAND=$(
    cat<<-'EOF'

    retval=$?

    RED=$(tput setaf 1)
    GREEN=$(tput setaf 2)
    STOP=$(tput sgr0)

    # arithmetic using bash parameter expansion on a array
    if (($retval + ${PIPESTATUS[@]/%/+} + 0)); then
        PS1="\[$RED\]\u@\h:\w$ \[$STOP\]"
    else
        PS1="\[$GREEN\]\u@\h:\w$ \[$STOP\]"
    fi
EOF
)

That will do the trick =)

Bash will run the code inside PROMPT_COMMAND for each commands.

If you have copy/paste problem, you can download the script

tput

EXPLANATIONS

  • (( )) is arithmetic in bash, see http://wiki.bash-hackers.org/syntax/arith_expr
  • PROMPT_COMMAND : if set, the value is executed as a command prior to issuing each primary prompt. See man bash | less +/PROMPT_COMMAND
  • tput is better than hard coding ANSI escape codes. See http://wiki.bash-hackers.org/scripting/terminalcodes
  • PIPESTATUS : An array variable containing a list of exit status values from the processes in the most-recently-executed foreground pipeline (which may contain only a single command). See man bash | less +/PIPESTATUS
  • cat<<-'EOF' is a special here doc : the - character means I can indent code, and the single quotes on 'EOF' means to not interpolate variables
  • Added doc link about your problem – Gilles Quénot Nov 20 '12 at 21:15
  • Thanks. I am getting an unexpected EOF while looking for matching \)'on the line that defines the function. Near as I can tell it seems that bash thinks that there is an unclosed(` in the do line, but I really don't see how that could be. – dotancohen Nov 20 '12 at 22:07
  • This is a copy & paste problem, this will disappear if you reindent. – Gilles Quénot Nov 20 '12 at 22:16
  • Actually, I did reindent, with tabs. Everything inside the function got one tab save for the lines that begin PS1, which got two tabs. – dotancohen Nov 20 '12 at 22:26
  • Try copy & paste again, there was some mistakes. – Gilles Quénot Nov 20 '12 at 22:31
  • wget http://pastie.org/pastes/5408715/download -O env.sh – Gilles Quénot Nov 20 '12 at 22:37
  • 1
    Thank you Sputnik! I learned a lot from this post, not only about the prompt but about other aspects as well. You were very helpful and I've found some goodies in you back posts as well that I'm going through to learn from. Thanks! – dotancohen Nov 20 '12 at 22:46
  • You're welcome dotancohen. Glad to help you. (sputnick not Sputnik) =) – Gilles Quénot Nov 20 '12 at 22:53
  • Removed for loop in favour of (( $retval + ${PIPESTATUS[@]/%/+} + 0)) using bash parameter expansion – Gilles Quénot Nov 21 '12 at 08:04
  • Thank you, sputnick, I have learned a ton from this wonderful post. Is there a reason for not simply checking the last status with $? = "0"? Also, if I might ask, where is the cat<<-'EOF' value used, and for what purpose is the EOF at the end of the function? I'm new to bash scripting and I've tried googling these questions but I seem to be missing for which keywords to google on. – dotancohen Nov 21 '12 at 10:11
  • Thanks @stArdustͲ, this is awesome! I have built a custom prompt some weeks ago. The battery status was not "updating", the way I would like. Your answer is the solution to my problem as well! I will post my custom PROMPT_COMMAND as an answer just to make it easier to read. – Nikos Alexandris Apr 06 '15 at 11:07
3

I have a 2-line prompt, so the potentially long stuff won't affect the command line:

glennj@homebase ~/tmp
2219 $ set | grep PS1=
PS1='\n\u@\h \w\n\! \$ '
glenn jackman
  • 85,964
  • So useful! I have had this in a custom prompt for http://grass.osgeo.org/ and now trying in the regular bash prompt too. It does away the non-clobbering problem. – Nikos Alexandris Nov 22 '15 at 07:10
1

This is not a direct answer to the original question, it is just a usage example.

Based and thanks to @stArdustͲ's answer, I found a proper solution for a custom command prompt to show up the battery's status*, on the top right corner of the terminal. The colors are defined in an independent file which is sourced from inside .bashrc.

PROMPT_COMMAND=$(
    cat<<-'EOF'
    function battery_percentage {
        PERCENTAGE=$(upower -i $(echo $(upower -e |grep 'battery')) |grep 'percentage' |sed -e 's/^[ \t]*//;s/[[:space:]]\+/ /g;s/[ \t]*$//' |cut -d' ' -f2 |cut -d"%" -f1)
        BColor='IGray'
        if [ "${PERCENTAGE}" -lt 15 ]
            then PColor='BlinkIRed'
                 PBackground='On_White'
            else PColor='Gray'
        fi
        echo "\001${!BColor}\002Battery\001${!PColor}\002 ${PERCENTAGE}% \001${Reset}\002"
    }

    function battery_time {
        TIME=$(upower -i $(echo $(upower -e |grep 'battery')) |grep 'time' |sed -e 's/^[ \t]*//;s/[[:space:]]\+/ /g;s/[ \t]*$//' |cut -d' ' -f4,5)
        echo "\001${IGray}\002${TIME}\001${Reset}\002"
    }

    export PS1="\[\e[s\]\[\e[1;\$((COLUMNS-26))f\]$(battery_percentage) $(battery_time)\[\e[u\]${PS1}"
EOF
)

Finally, the PS1 looks like:

\[\e[s\]\[\e[1;$((COLUMNS-26))f\]\001\e[0;38;5;8m\002Battery\001\e[0;38;5;7m\002 82% \001\e[0m\002 \001\e[0;38;5;8m\0023.6 hours\001\e[0m\002\[\e[u\]\[\e[0;38;5;232m\]\[\e[48;5;5m\] \u \[\e[0;38;5;5m\]\[\e[48;5;11m\] \[\e[0;38;5;232m\]\[\e[48;5;11m\]@\H \[\e[0;38;5;11m\]\[\e[48;5;0m\]\[\e[0m\] \[\e[0;38;5;6m\]\w\a \[\e[1;38;5;5m\]→\e[0m

Note, I do have some cursor positioning problems, though, when I scroll up and down through the command history.


* of a ThinkPad laptop

1

Not \\[, just \[. You also have an extra \[ at the start.

Jim Paris
  • 14,337