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?

8

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 Ask Ubuntu 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
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 \[..\].)

  • 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. – Tonin Dec 19 '13 at 20:58
1

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.

  • For future reference, use a bash function in your .bashrc - don't put code in a separate file and call that. – starbeamrainbowlabs Jan 14 '16 at 8:03

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.