2

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` (surrounded by backticks) 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?

  • 1
  • 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 Dec 4 '17 at 2: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 4 '17 at 8:36
1

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)

  • Thank you. It's not an ideal solution, as it leaves variables smile and smilecolor public, but at least it works. – Szymon Dec 4 '17 at 7: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 4 '17 at 8:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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