Bash prompt: using \[ and \] within PROMPT_COMMAND

bashbashrcprompt

I'm working on a bash prompt. I'm also trying to create the prompt intelligently, so that it will be easily readable and maintainable. That means not having one huge export PS1.

Various sources (including this question) reference the need for \[ and \] around formatting, to help bash know not to overwrite the prompt with long commands.

When creating the PROMPT_LAST_EXIT_STATUS string, the intention is to either show the exit status of the last command ($?) in red, or nothing (if $? was 0). This works, but literal [s and ]s are showing up in the prompt, and the long command issue is still present. It's probably an escaping issue somewhere, but I haven't found it.

Unexpected square braces

The following code is in ~/.bashrc.

prompt_last_exit_status () {
PROMPT_LAST_EXIT_STATUS="${?}";
if [[ ${PROMPT_LAST_EXIT_STATUS} == "0" ]];
then
    PROMPT_LAST_EXIT_STATUS=
else
    PROMPT_LAST_EXIT_STATUS=\[$(tput setaf 1)$(tput bold)\]${PROMPT_LAST_EXIT_STATUS}
    PROMPT_LAST_EXIT_STATUS+=\[$(tput sgr0)\]
    PROMPT_LAST_EXIT_STATUS+=" "
fi;
}


prompt_command () {
    prompt_last_exit_status
}
export PROMPT_COMMAND=prompt_command

PS1="\${PROMPT_LAST_EXIT_STATUS}"
PS1+="\[$(tput setaf 6)$(tput bold)\]\w"
PS1+="\[$(tput sgr0)\] \$ \[$(tput sgr0)\]"
export PS1

Best Answer

Your assignments to PROMPT_LAST_EXIT_STATUS aren't being quoted, so you're not putting \[ and \] into the string, you're just putting [ and ] (because the \s are being treated as escape characters).

Compare:

$ foo=\[hello\]
$ echo "$foo"
[hello]

Vs:

$ foo="\[hello\]"
$ echo "$foo"
\[hello\]

Not only that: parameter expansion (the interpolation of variables into the prompt string) happens after the the expansion of prompt special characters. So, putting the \[ and \] into the PROMPT_LAST_EXIT_STATUS variable won't work, as by the time $PROMPT_LAST_EXIT_STATUS is expanded, the \[ and \] are no longer special. A working alternative would move the color setting to be unconditional, something like:

prompt_last_exit_status () {
    PROMPT_LAST_EXIT_STATUS="${?}"
    if [[ ${PROMPT_LAST_EXIT_STATUS} == "0" ]]
    then
        PROMPT_LAST_EXIT_STATUS=
    else
        PROMPT_LAST_EXIT_STATUS+=" "
    fi
}

prompt_command () {
    prompt_last_exit_status
}
export PROMPT_COMMAND=prompt_command

PS1="\[$(tput setaf 1)$(tput bold)\]\${PROMPT_LAST_EXIT_STATUS}\[$(tput sgr0)\]"
PS1+="\[$(tput setaf 6)$(tput bold)\]\w"
PS1+="\[$(tput sgr0)\] \$ \[$(tput sgr0)\]"
export PS1
Related Question