Understanding terminal control keys

22.08.20    tools and tricks

Interactive terminal applications are controlled via keyboard commands. Examples of such programs include:

  • man page reader man
  • the file manager ranger
  • the text editor nano

Many commands involve the use of the control key which most commonly (in the context of the terminal) is signified using a caret and a capital letter. So ^B is a command activated by holding the control key and hitting the b key. But if you have used terminal programs for a non-trivial amount of time, then you know that not all control keys are made equal: ^M is always the same as hitting the enter key. ^I is the same as tab. ^C tries to end the current program1. And how are you supposed to use keys like ^_ or ^^ (on non-US keyboards anyway)? To untangle this mess we have to dig a little deeper and understand the mechanisms at play here.

The first thing to realize is that the input for all terminal programs - non-interactive and interactive - is a stream of characters. For interactive programs all the keyboard input is encoded into it. When you hit the b key then the program receives the character b on the input stream2. While this system is easy and simple, it is also restrictive. The program does not receive separate key down und up events. And it also cannot handle the shift key for example because it is not associated with any character itself and instead transforms the other characters sent to the program.

And the same is true for the control key! It also transforms the character code sent to the program. To understand how, we'll have a look at the table of ASCII3 codes used to encode the program's input:

ASCII Table

The particular layout of this ASCII tables makes it easy to understand how it works. When you hold the control key, characters from the columns 4 and 5 are transformed into the corresponding codes from columns 0 and 1. And this is why for example ^I and the tab key are indistinguishable for terminal programs: both send the ASCII code 9 for HT ("horizontal tab") to the input stream.

Most of these codes in column 0 and 1 can be created with the letters A-Z. This leaves only 6 other codes associated with other control key combinations: ^@ (at the start of column 4 / 0), ^[, ^\, ^], ^^, ^_ (at the end of column 5 / 1).

These key combinations can be entered "normally" on US and similar keyboards by holding the control key and hitting the correspondingly labelled key (regardless of whether the symbol is normally associated with the standard of shift-variant of the key). Thankfully most of the time there are alternatives for other keyboard layouts. We can read from the table that ^[ is the same as ESC - the escape key. And for the whole group: Most graphical terminal emulators produce the ASCII codes ^@ to ^] by holding control and hitting a key between 2 and 7. Here the simplistic nature of the input mechanism allowed implementing an easier input scheme which is completely transparent to the terminal programs. So for example marking text in nano via the ^^-command is activated with Control-6 (on both the US keyboard and in the alternative scheme):

Caret Notation ASCII Number ASCII Name Alternative Key Note
^@ 0 NUL Control-2
^[ 27 ESC Control-3 Escape key
^\ 28 FS Control-4 sends SIGQUIT
^] 29 GS Control-5
^^ 30 RS Control-6
^_ 31 US Control-7

  1. Actually this just sends a signal. The default behaviour is to terminate the program. But programs can override the signal handler or even stop the signal from being sent. This special behaviour is actually implemented inside virtual devices called pseudottys which intercept the key and translate it to a signal. 

  2. This is true of all character-like keys. Others like the arrow keys are encoded into special code sequences called escape sequences. 

  3. In modern contexts this is most often extended to UTF-8 to allow encoding any Unicode character. Because ASCII is a subset of UTF-8 these are still the codes sent to the programs. The relevant control characters are the same.