13

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 Ctrl-r.
  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?

l0b0
  • 51,350

3 Answers3

9

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\] " ")$ '
  • No need to go to [ubuntu.se] for it. We already have enough answers to this question here too. – manatwork Dec 19 '13 at 18:30
  • Thank you for the advice @manatwork! I wanted to give proper credit for the explanation and supplied the reference as a courtesy. – Timothy Martin Dec 19 '13 at 18:36
  • Giving credit is not a problem. But while talking about problem: unescaped backslashes used to vanish from Markdown, so your plain \[ became [ in your post, thus the displayed code was not functional by copy-pasting it into terminal. This can be avoided by using inline code or code block markup. (How do I format my posts using Markdown or HTML?) – manatwork Dec 19 '13 at 18:47
  • 1
    D'oh! I've already fixed the same issue for other PS1 code, why didn't I see that one? – l0b0 Dec 19 '13 at 21:05
2

Expanding on @manatwork answer but keeping your code splitting the PS1 compute in different functions, you can write your prompt the following way:

set_exit_code() {
    exit_code=$?
    [[ $exit_code -eq 0 ]] || printf "\[$(tput setaf 1)\] $exit_code \[$(tput sgr0)\] "
}
set_bash_prompt() {
    PS1="$(set_exit_code)$ " # with double quotes!
}
PROMPT_COMMAND=set_bash_prompt

Double quotes are mandatory both when setting PS1 and when using printf in the function.

Lætitia
  • 540
1
PS1='$(exit_code=$?; [[ $exit_code -eq 0 ]] || printf %s \[$(tput setaf 1)\] $exit_code \[$(tput sgr0)\] " ")$ '

(Sorry, no explanation here. See How to customize PS1 properly? or any other question about prompt length calculation issues and \[..\].)

manatwork
  • 31,277
  • To second @l0b0 question, I'll add that using PS1 and \[...\] works fine as long as you can put all the code you want to generate your prompt in a single string. However, if you want to split your code into small functions, you come to a point where you cannot put the starting and ending brackets in the same string/function. And that breaks line wrap. Unless you resort to using PROMPT_COMMAND to recompute your PS1 at each prompt. – Lætitia Dec 19 '13 at 20:58