5

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

2 Answers 2

3

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
4
  • True. Tried quoting the two assignments which weren't, output becomes \[\]127\[\] instead of []127[]. Something is treating them as literals instead of format characters. Any ideas?
    – David Lord
    May 28, 2015 at 22:02
  • 1
    Aha. Yeah, still something missing. Let me update my answer...
    – godlygeek
    May 28, 2015 at 22:13
  • Works great, thanks. To be sure I understand you, you're saying that it won't be possible to store \[s as special characters within a variable, and so we can't keep formatting with the variables used in the prompt? We have to do it during 'final' generation of PS1?
    – David Lord
    May 29, 2015 at 1:55
  • 1
    It could be put into a variable that's expanded when you're assigning a value to PS1, but not into a variable that's expanded when the prompt is being drawn. Consider x="\[\]"; PS1="$x or \$x" - you wind up with a prompt that says " or []". The 1st $x is expanded when assigning to PS1 (so the \[\] is part of the prompt string, and doesn't print anything), the 2nd is escaped so it will be expanded only when the prompt is drawn (and after prompt escapes have been processed, so it prints).
    – godlygeek
    May 29, 2015 at 18:21
1

PS1 evaluates \[ and \] as 1 and 2. If you don't want to put \[ and your coloring in your PS1 because it's so long, you can have PS1 use variables that already include 1 and 2. A variable will contain 1 if you use $'\001' and will contain 2 if you use $'\002'. I've modified your code to work as written.

prompt_last_exit_status () {
    PROMPT_LAST_EXIT_STATUS=$?
    if [[ ${PROMPT_LAST_EXIT_STATUS} == "0" ]]; then
        PROMPT_LAST_EXIT_STATUS=
    else
        PROMPT_LAST_EXIT_STATUS=$'\001'$(tput setaf 1)$(tput bold)$'\002'$PROMPT_LAST_EXIT_STATUS
        PROMPT_LAST_EXIT_STATUS+=$'\001'$(tput sgr0)$'\002'
        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

Extra Info

You can also have PS1 reference a function that will be called when PS1 is evaluated.

print_status() {
    printf "\001$(tput setaf 1)$(tput bold)\002$?\001$(tput sgr0)\002"
}
export -f print_status
export PS1='$(print_status) '

NOTE: Exporting the function print_status will ensure that when you su, PS1 will be able to reference the function on which it depends. Also, make sure any exported function will work with the standard Bourne shell (sh) as git and vim use these shells and will try to use these functions.

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.