Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash PS1: line wrap issue with non-printing characters from an external command

I am using an external command to populate my bash prompt, which is run each time PS1 is evaluated. However, I have a problem when this command outputs non-printable characters (like color escape codes). Here is an example:

$ cat green_cheese.sh 
#!/bin/bash
echo -e "\033[32mcheese\033[0m"

$ export PS1="\$(./green_cheese.sh) \$"
cheese $ # <- cheese is green!
cheese $ <now type really long command>

The canonical way of dealing with non-printing characters in the PS1 prompt is to enclose them in \[ and \] escape sequences. The problem is that if you do this from the external command those escapes are not parsed by the PS1 interpreter:

$ cat green_cheese.sh 
#!/bin/bash
echo -e "\[\033[32m\]cheese\[\033[0m\]"
$ export PS1="\$(./green_cheese.sh) \$"
\[\]cheese\[\] $ # <- FAIL!

Is there a particular escape sequence I can use from the external command to achieve the desired result? Or is there a way I can manually tell the prompt how many characters to set the prompt width to?

Assume that I can print anything I like from the external command, and that this command can be quite intelligent (for example, counting characters in the output). I can also make the export PS1=... command as complicated as required. However, the escape codes for the colors must come from the external command.

Thanks in advance!

like image 362
Lee Netherton Avatar asked Jul 19 '14 10:07

Lee Netherton


1 Answers

I couldn't tell you exactly why this works, but replace \[ and \] with the actual characters that bash generates from them in your prompt:

echo -e "\001\033[32m\002cheese\001\033[0m\002"

[I learned this from some Stack Overflow post that I cannot find now.]

If I had to guess, it's that bash replaces \[ and \] with the two ASCII characters before executing the command that's embedded in the prompt, so that by the time green_cheese.sh completes, it's too late for bash to process the wrappers correctly, and so they are treated literally. One way to avoid this is to use PROMPT_COMMAND to build your prompt dynamically, rather than embedding executable code in the value of PS1.

prompt_cmd () {
    PS1="$(green_cheese.sh)"
    PS1+=' \$ '
}

PROMPT_COMMAND=prompt_cmd

This way, the \[ and \] are added to PS1 when it is defined, not when it is evaluated, so you don't need to use \001 and \002 directly.

like image 192
chepner Avatar answered Oct 17 '22 03:10

chepner