bashescapingzshansi-escape

Mixing \e and other escape characters


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

Solution

  • 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 "^["):

    Comparison of visible results in different terminal programs

    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: