5

I wanted to modify my PS1 to run some commands every time. Let's say I want it so that if the last executed command was successful, it would add a green smile at the end of PS1, otherwise the smile should be red.
I extracted it to a function:

function exit_smile {

    EXITSTATUS="$?"
    RED="\[\e[1;31m\]"
    GREEN="\[\e[32;1m\]"

    if [ "${EXITSTATUS}" -eq 0 ]
    then
       SMILE="${GREEN}:)"
    else
       SMILE="${RED}:("
    fi

    echo -n "$SMILE"
}

and then tried both using `exit_smile` and \$(exit_smile) when modifying the PS1 variable, but it executes it once when modifying PS1 or prints literal \[\e...\] instead of a color.
For example

PROMPT="\u@\h \W"
PS1="${PROMPT} \$ \$(exit_smile) ${OFF}\n"

Gives username@hostname ~ $ \[\e[32;1m\]:)
What am I missing?

terdon
  • 234,489
  • 66
  • 447
  • 667
Szymon
  • 163
  • 5
  • 1
    Possible duplicate of [How to include commands in Bash's PS1 without breaking line length calculation?](https://unix.stackexchange.com/questions/105926/how-to-include-commands-in-bashs-ps1-without-breaking-line-length-calculation) – jasonwryan Dec 03 '17 at 22:09
  • I believe that you need to put the string in single quotes, or add an extra layer of backslashes. To test, do `printf "%s\n" "$PS1"`; it should show all the dollar signs that you want to have evaluated when the prompt is issued. – G-Man Says 'Reinstate Monica' Dec 04 '17 at 02:08
  • @jasonwryan, The line length calculation breaks because of non-printing characters, and the solution is to add those `\[..\]`. They're already in place here, so this isn't much of a duplicate – ilkkachu Dec 04 '17 at 08:36

1 Answers1

4

I'm not sure if this has changed between versions(*), but my man page for Bash says that

Bash allows these prompt strings to be customized by inserting a number of backslash-escaped special characters that are decoded as follows:

(list contains \e, \[, \] etc.)

After the string is decoded, it is expanded via parameter expansion, command substitution, ...

Which means that the \[..\] can't come from the command substitution, but must be there before that.

(It also means you could use \u or \w as arguments to a command substitution, and they'd get replaced before the command runs. And I have no idea what putting \[..\] inside a command substitution would do... This would make more sense the other way around.)

So, we'll have to put the color codes in separate expansions and protect them with \[..\] by hand. I'll use variables instead of command substitution, and also the $'...' expansion to get the ESC character:

prompt_smile() {
        if [ "$?" = 0 ] ; then
                smile=' :) '
                smilecolor=$'\e[1;32m'
        else
                smile=' :( '
                smilecolor=$'\e[1;31m'
        fi
        normalcolor=$'\e[0m'
}

PROMPT_COMMAND=prompt_smile
PS1='\u@\h \W \$ \[$smilecolor\]$smile\[$normalcolor\]\n'

(* the reason I wonder about that, is that the answers to the older and similar but no so duplicate question seem to output the \[..\] from within an expansion)

ilkkachu
  • 133,243
  • 15
  • 236
  • 397
  • Thank you. It's not an ideal solution, as it leaves variables `smile` and `smilecolor` public, but at least it works. – Szymon Dec 04 '17 at 07:59
  • @Szymon, they're only visible within your shell (unless you export them). Rename them something like `__p_smile` etc, and it'll be quite hard to hit them accidentally. – ilkkachu Dec 04 '17 at 08:37