12

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.

  • why do you think the shell is highlighting them? for bash it is readline handling that stuff, and for most others it is the tty driver. – mikeserv Oct 30 '15 at 16:51
  • I didn't know. That is why I wrote the note. It was just my guess because other applications like vi and less often highlight control characters differently from regular text. I will edit the question with this information. Thank you! – wefwefa3 Oct 30 '15 at 17:01
  • that makes sense. that kind of stuff is more difficult for a shell because its kind of a second priority. with editors their whole job is to provide a screen <> file interface, but the shell is a command interpreter for an interpreted language first and a line editor second (or not at all). – mikeserv Oct 30 '15 at 17:06
  • Readline is used by other programs, but also it is part of bash: it is a library linked into it. And today's shells, especially bash, do pretty much anything you can think up. – alexis Oct 30 '15 at 18:16
  • Just use zsh which does that out of the box. – Stéphane Chazelas Nov 17 '15 at 15:57
19
+50

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.

3

I usally have this code in my .bashrc file:

function get_exit_status()
{
        local code=$?
        if [ $code -ne 0 ]
        then
                printf $'\001\033[31m\002'"($code)"$'\001\033[0m\002'" "
        fi
}

and then I call this function in my PS1

PS1='\u@\h \w $(get_exit_status)'

With this, if you press ^C you will see it in your prompt

I@mycomputer ~ ^C
I@mycomputer ~ (130)

All exit status codes that are not "0" will be prompted.

  • $? expands to exit status in the PS1 e.g PS1='$?\u@\h \w ' – teknopaul Apr 29 at 16:33

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.