Bash – Display Control Characters Differently in the Shell

bashcolorsdisplayreadlinespecial characters

When you type control characters in the shell they get displayed using what is called "caret notation". Escape for example gets written as ^[ in caret notation.

I like to customize my bash shell to make it look cool. I have for example changed my PS1 and PS2 to become colorized. I now want control characters to get a unique appearance as well to make them more distinguishable from regular characters.

$ # Here I type CTRL-C to abort the command.
$ blahblah^C
          ^^ I want these two characters to be displayed differently

Is there a way to make my shell highlight control characters differently?

Is it possible to make it display them in a bold font or maybe make them appear in different colors from regular text?

I am using bash shell here but I did not tag the question with bash because maybe there is a solution that applies to many different shells.

Note that I do not know at what level highlighting of control characters takes place. I first thought it was in the shell itself. Now I have heard that it is readline that controls how control characters are in shells like bash. So the question is now tagged with readline and I am still looking for answers.

Best Answer

When you press Ctrl+X, your terminal emulator writes the byte 0x18 to the master side of the pseudo-terminal pair.

What happens next depends on how the tty line discipline (a software module in the kernel that sits in between the master side (under control of the emulator) and the slave side (which applications running in the terminal interact with)) is configured.

A command to configure that tty line discipline is the stty command.

When running a dumb application like cat that is not aware of and doesn't care whether its stdin is a terminal or not, the terminal is in a default canonical mode where the tty line discipline implements a crude line editor.

Some interactive applications that need more than that crude line editor typically change those settings on start-up and restore them on leaving. Modern shells, at their prompt are examples of such applications. They implement their own more advanced line editor.

Typically, while you enter a command line, the shell puts the tty line discipline in that mode, and when you press enter to run the current command, the shell restores the normal tty mode (as was in effect before issuing the prompt).

If you run the stty -a command, you'll see the current settings in use for the dumb applications. You're likely to see the icanon, echo and echoctl settings being enabled.

What that means is that:

  • icanon: that crude line editor is enabled.
  • echo: characters you type (that the terminal emulator writes to the master side) are echoed back (made available for reading by the terminal emulator).
  • echoctl: instead of being echoed asis, the control characters are echoed as ^X.

So, let's say you type A B Backspace-aka-Ctrl+H/? C Ctrl+X Backspace Return.

Your terminal emulator will send: AB\bC\x18\b\r. The line discipline will echo back: AB\b \bC^X\b \b\b \b\r\n, and an application that reads the input from the slave side (/dev/pts/x) will read AC\n.

All the application sees is AC\n, and only when your press Enter so it can't have any control on the output for ^X there.

You'll notice that for echo, the first ^H (^? with some terminals, see the erase setting) resulted in \b \b being sent back to the terminal. That's the sequence to move the cursor back, overwrite with space, move cursor back again, while the second ^H resulted in \b \b\b \b to erase those two ^ and X characters.

The ^X (0x18) itself was being translated to ^ and X for output. Like B, it didn't make it to the application, as we deleted it with Backspace.

\r (aka ^M) was translated to \r\n (^M^J) for echo, and \n (^J) for the application.

So, what are our options for those dumb applications:

  • disable echo (stty -echo). That effectively changes the way control characters are echoed, by... not echoing anything. Not really a solution.
  • disable echoctl. That changes the way control characters (other than ^H, ^M... and all the other ones used by the line editor) are echoed. They are then echoed as-is. That is for instance, the ESC character is send as the \e (^[/0x1b) byte (which is recognised as the start of an escape sequence by the terminal), ^G you send a \a (a BEL, making your terminal beep)... Not an option.
  • disable the crude line editor (stty -icanon). Not really an option as the crude applications would become a lot less usable.
  • edit the kernel code to change the behaviour of the tty line discipline so the echo of a control character sends \e[7m^X\e[m instead of just ^X (here \e[7m usually enables reverse video in most terminals).

An option could be to use a wrapper like rlwrap that is a dirty hack to add a fancy line editor to dumb applications. That wrapper in effect tries to replace simple read()s from the terminal device to calls to readline line editor (which do change the mode of the tty line discipline).

Going even further, you could even try solutions like this one that hijacks all input from the terminal to go through zsh's line editor (which happens to highlight ^Xs in reverse video) relying on GNU screen's :exec feature.

Now for applications that do implement their own line editor, it's up to them to decide how the echo is done. bash uses readline for that which doesn't have any support for customizing how control characters are echoed.

For zsh, see:

info --index-search='highlighting, special characters' zsh

zsh does highlight non-printable characters by default. You can customize the highlighting with for instance:

zle_highlight=(special:fg=white,bg=red)

For white on red highlighting for those special characters.

The text representation of those characters is not customizable though.

In a UTF-8 locale, 0x18 will be rendered as ^X, \u378, \U7fffffff (two unassigned unicode code points) as <0378>, <7FFFFFFF>, \u200b (a not-really printable unicode character) as <200B>.

\x80 in a iso8859-1 locale would be rendered as ^�... etc.

Related Question