Differences between revisions 10 and 11
Revision 10 as of 2015-04-15 07:04:52
Size: 5184
Editor: 195
Comment: add the \001 \002 trick
Revision 11 as of 2015-04-15 07:07:52
Size: 5185
Editor: 212
Comment: type
Deletions are marked like this. Additions are marked like this.
Line 21: Line 21:
# this function runs when the prompt is diplayed # this function runs when the prompt is displayed

I have a fancy prompt with colors, and now bash doesn't seem to know how wide my terminal is. Lines wrap around incorrectly.

Escape the colors with \[ \]

You must put \[ and \] around any non-printing escape sequences in your prompt. Thus:

   1 fancy_prompt() {
   2   local blue=$(tput setaf 4)
   3   local purple=$(tput setaf 5)
   4   local reset=$(tput sgr0)
   5   PS1="\[$blue\]\h:\[$purple\]\w\[$reset\]\\$ "
   6 }

Without the \[ \], bash will think the bytes which constitute the escape sequences for the color codes will actually take up space on the screen, so bash won't be able to know where the cursor actually is.

Escape the colors with \001 \002 (dynamic prompt or read -p)

The \[ \] are only special when you assign PS1, if you print them inside a function that runs when the prompt is displayed it doesn't work. In this case you need to use the bytes \001 and \002:

   1 # this function runs when the prompt is displayed
   2 active_prompt () {
   3   local blue=$(tput setaf 4)
   4   local reset=$(tput sgr0)
   5   printf '\001%s\002%s\001%s\002' "$blue" "$PWD" "$reset"
   6 }
   7 
   8 PS1='$(active_prompt)\$ '

If you want to use colors in the "read -p" prompt, the wrapping problem also occurs and you cannot use \[ \], you must also use \001 \002 instead:

   1   blue=$(tput setaf 4)
   2   reset=$(tput sgr0)
   3   read -p $'\001'"$blue"$'\002'"what is your favorite color?"$'\001'"$reset"$'\002' answer

If you still have problems, e.g. when going through your command history with the Up/Down arrows, make sure you have the checkwinsize option set:

   1 shopt -s checkwinsize

Refer to the Wikipedia article for ANSI escape codes.

More generally, you should avoid writing terminal escape sequences directly in your prompt, because they are not necessarily portable across all the terminals you will use, now or in the future. Use tput to generate the correct sequences for your terminal (it will look things up in your terminfo or termcap database).

Since tput is an external command, you want to run it as few times as possible, which is why we suggest storing its results in variables, and using those to construct your prompt (rather than putting $(tput ...) in PS1 directly, which would execute tput every time the prompt is displayed). The code that constructs a prompt this way is much easier to read than the prompt itself, and it should work across a wide variety of terminals. (Some terminals may not have the features you are trying to use, such as colors, so the results will never be 100% portable in the complex cases. But you can get close.)


  • Personal note: I still prefer this answer:

       1 BLUE=$(tput setaf 4)
       2 PURPLE=$(tput setaf 5)
       3 RESET=$(tput sgr0)
       4 PS1='\[$BLUE\]\h:\[$PURPLE\]\w\[$RESET\]\$ '
    

    I understand that people like to avoid polluting the variable namespace; hence the function and the local part, which in turn forces the use of double quotes, which in turn forces the need to double up some but not all backslashes (and to know which ones -- oy!). I find that unnecessarily complicated. Granted, there's a tiny risk of collision if someone overrides BLUE or whatever, but on the other hand, the double-quote solution also carries the risk that a terminal will have backslashes in its escape sequences. And since the contents of the escape sequences are being parsed in the double-quote solution, but not in the single-quote solution, such a terminal could mess things up. Example of the difference:

       1  imadev:~$ FOO='\w'; PS1='$FOO\$ '
       2  \w$ FOO='\w'; PS1="$FOO\\$ "
       3  ~$ 
    

    Suppose our terminal uses \w in an escape sequence. A \w inside a variable that's referenced in a single-quoted PS1 is only expanded out to a literal \w when the prompt is printed, which is what we want. But in the double-quoted version, the \w is placed directly into the PS1 variable, and gets evaluated by bash when the prompt is printed. Now, I don't actually know of any terminals that use this notation -- it's entirely a theoretical objection. But then again, so is the objection to the use of variables like BLUE. And some people might actually want to echo "$BLUE" in their shells anyway. So, I'm not going to say the single-quote answer is better, but I'd like to see it retained here as an alternative. -- GreyCat

    • Fair enough. I initially just intended to change the BLACK= to a RESET= (since not everyone uses white on black), but then I thought it would be better if the prompt did not depend on variables being available. I obviously was not aware about the possibility of such terminal escape sequences, so I think mentioning the single-quote version first would be a better idea and also mention what happens if those vars change.

      I guess one could also make the variables readonly to prevent accidentally changing them and mess up the prompt, though that'll probably have other drawbacks..? -- ~~~

BashFAQ/053 (last edited 2022-09-14 02:03:48 by emanuele6)