Bash – Emit zero-width bash prompt sequence from external binary

bashprompt

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?

Best Answer

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
Related Question