I recently came to know how ANSI-C quoted strings can be used to give colourful prompts to commands that do not recognize escape characters. For e.g.
% read -q $'val? \e[31mFoobar\e[0m:'
Foobar: # Prints 'Foobar' in red
So, I was wondering what the ANSI-C string looks like when it is passed to the read command. I just created a sample file to print the individual characters
# test.sh
foo=$1
for (( i=0; i<${#foo}; i++ )); do
echo "${foo:$i:1}"
done
and this is the output
% ./test.sh $'\e[31mFoobar\e[0m:'
F
o
o
b
a
r
: # Prints each 'Foobar' character in red
So, I was a little confused after seeing the output and ran this
% echo $'\e\b[\b3\b1\bm\bFoobar'
Foobar # Prints 'Foobar' in red
% echo $'\e\n[\n3\n1\nm\nFoobar'
Foobar # Prints 'Foobar' in red
% echo $'\e\t[\t3\t1\tm\tFoobar'
[ 3 1 m Foobar #Doesn't print 'Foobar' in red
As seen above, clearly in some cases the escape characters are not stopping the color. What is happening here?
I'm on zsh 5.9 (arm64-apple-darwin24.0)
on MacOS Sequoia 15.2
. Although, I worked using zsh
the same even happens in bash
Edit: Adding the output of printf "TERM=${TERM}\n"
% printf "TERM=${TERM}\n"
TERM=xterm-256color
The output is confusing because Apple's Terminal utility (which I presume you're using) is weirdly tolerant of interrupted escape sequences. Here's a comparison of what this command looks like in (from left to right) Apple's Terminal, iTerm2, and then Terminal again but piped through cat -vt
to convert control characters to visible representations (in this case, the escape -- aka Control-[ -- gets converted to "^["):
The rightmost version shows what's output most clearly -- there's an escape printed on the first line, "[" on the second, "3" on the third, etc. The iTerm2 version is similar, but the escape is not visible.
Only the leftmost version (Terminal without filtering) looks weird. What seems to be happening is that when Terminal receives linefeed (aka newline) and/or carriage return characters partway through an escape sequence, it doesn't consider them to interrupt the escape sequence. So in the leftmost version it's recognizing the escape sequence as an escape sequence even though it's spread over 5 lines. So the characters in (what it considers to be) the sequence don't get printed, and the sequence does affect the color of the text that does get printed.
BTW, in case you're wondering where the carriage returns I mentioned above come from, they're added by the terminal driver. The output goes from the program (bash in this case), to the terminal driver (which converts linefeeds into carriage return + linefeed pairs), then to the terminal application (which decides what to show on screen, based on what it gets from the terminal driver). So what you see on screen is a complex result of all three of their contributions/influences.
BTW2: My favorite methods to see exactly what a program is outputting is to pipe through either:
LC_ALL=C cat -vet
, which converts control characters to visible representations, and adds a "$" at the end of each line to show explicitly where it actually ends, and converts non-ASCII (e.g. unicode) characters to weird-looking sequences so you can tell there's something there.
od -c
, which makes plain text hard to read, but shows nonprintable characters very explicitly as either escape sequences (e.g. newline -> "\n") or raw octal codes. You can also prefix this with LC_ALL=C
to make it print unicode (or other non-ASCII) characters as sequences of octal bytes.