6

In bash, how do I encode zero-width sequences into PS1, when those sequences are coming from stdout of an external process or function? How do I implement writes-prompt-sequences-to-stdout so that it can emit multi-colored text to the prompt?

PS1='$( writes-prompt-sequences-to-stdout )'


I know that, when writing a bash PS1 prompt, I must wrap zero-width sequences in \[ \] so bash can compute correct prompt width.

PS1='\[\e[0;35m\]$ \[\e[00m\]' bash does not print the \[ \] and understands the prompt is only 2 characters wide.

How do I move those sequences into an external function? The following does not work, my prompt looks like \[\]$ \[\], even though I can run render-prompt and see it writing the correct sequence of bytes to stdout.

PS1='$( render-prompt )'
function render-prompt {
    printf '\[\e[0;35m\]$ \[\e[00m\]'
}

Moving the printf call into PS1 does work:

PS1='$( printf '"'"'\[\e[0;35m\]$ \[\e[00m\]'"'"' )'

I theorized, perhaps bash is scanning the PS1 string before execution to count the number of zero-width bytes. So I tried tricking it by encoding [] sequences that aren't printed, but it correctly ignores the trick.

PS1='$( printf '"'"'$$$$$'"'"' '"'"'\[\e[00m\]'"'"' )'

My question:

How do I write \[ \] sequences to stdout from a function or binary that is invoked via PS1?

4

I figured it out. Bash special-cases \e, \[, and \] within PS1. It coverts \e to an escape byte, \[ to a 1 byte, and \] to a 2 byte. External commands must write 1 and 2 bytes to stdout.

According to ASCII, these encode "start of heading" and "start of text."

http://www.columbia.edu/kermit/ascii.html

Here's a working example, which relies on printf converting \ escapes within the first positional parameter into the correct bytes:

PS1='$( render-prompt )'
function render-prompt {
  printf '\1\033[0;35m\2$ \1\033[00m\2'
}
render-prompt | hexdump -C
00000000  01 1b 5b 30 3b 33 35 6d  02 24 20 01 1b 5b 30 30  |..[0;35m.$ ..[00|
00000010  6d 02                                             |m.|
00000012
1

You correctly figured out the \1, \2 and \e. But there's a better and more correct way to produce the sequences.

The printf command takes FORMAT as the first argument. This format consumes the subsequent arguments. So your function could be better written this way:

render-prompt () { printf "\1%b\2%s\1%b\2" '\e[0;35m' '$ ' '\e[00m'; }

The first argument is "\1%b\2%s\1%b\2" and this is the format. It's a good and logical place for \1 and \2, which embrace the %b parameters. (The %bs are \-escaped strings and %s represents a standard string.)

The others are '\e[0;35m', '$ ' and '\e[00m', which correspond to %b, %s and %b in the format. Here I'd put \es, as they escape the [ braces they precede.

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.