Bash – How to Include Commands in PS1 Without Breaking Line Length Calculation

bashprompt

Tonin pointed out a bug in my default prompt. Minimal example:

  1. Set PS1:

    PS1='$(exit_code=$?; [[ $exit_code -eq 0 ]] || printf %s $(tput setaf 1) $exit_code $(tput sgr0) " ")$ '
    

    At this point, the prompt looks like this:

    $ 
    
  2. Now trigger the exit code output by running:

    false
    

    Now the prompt contains the exit code in red at the beginning of the line:

    1 $ 
    
  3. Press Ctrlr.
  4. Type "false". Now the prompt contains only the search:

    (reverse-i-search)`false': false
    
  5. Press Enter.

The resulting terminal history now contains the following:

1 $ch)`false': false

Expected output:

1 $ false

That is, it seems the history search output is mixed with the prompt and hiding the actual command which was run.

I tried working around this by using PROMPT_COMMAND:

set_exit_code() {
    exit_code=$?
    [[ $exit_code -eq 0 ]] || printf %s $(tput setaf 1) $exit_code $(tput sgr0) " "
}
set_bash_prompt() {
    PS1='$(set_exit_code)$ ' # Double quotes give the same result
}
PROMPT_COMMAND=set_bash_prompt

This doesn't seem to work – the line looks exactly the same as before after searching and running.

How can I fix this?

Best Answer

I found the answer on askubuntu.com. @qeirha mentioned that you have to tell bash that the sequence of characters should not be counted in the prompt's length, and you do that by enclosing it in \[ \]. Based on the example provided, here is one solution:

red=$(tput setaf 1)

reset=$(tput sgr0)

[ "$PS1" = "\\s-\\v\\\$ " ] && PS1='$(exit_code=$?; [[ $exit_code -eq 0 ]] || printf %s \[$red\] $exit_code \[$reset\] " ")$ '
Related Question