Printing/Converting Control Characters


Introduction: Keys and Codes

Most of the keys on the keyboard have numeric codes in the ASCII system. See man 7 ascii for a table of keys and codes. For example, lower case a has code 97. If you press the Shift key and the a key, you get the upper case A, which has code 65. Instead, if you press the Ctrl key and the a key, you get Ctrl-A which has ASCII code 1. The ASCII table does not have a separate code for Ctrl-Shift-A; most terminal drivers represent that with the same code as Ctrl-A.

Printing Regular and Control Characters

The ASCII table has two kinds of characters: printable characters and control characters. Printable characters include all the letters, digits, punctuation, and the space character. These characters have ASCII codes 32 through 126.

To print a printable character, use putchar(c) or printf("%c", c).

Control characters include ^A, ^B, .., ^Z, ^[, ^\, ^], ^^, ^_, and the Delete key. Some of the control characters have their own keys. For example, the Enter key sends ^M (carriage return), the Backspace key sends ^H, and the Tab key sends code ^I. You press those dedicated keys or you can hold the Ctrl key and press M, or H, or I.

You can use putchar and printf with control characters, but the output will look strange or blank. The usual way of representing these keys is as a caret (^) followed by a character. For example, the two-character sequence "^B" is used to represent the Ctrl-B key combination, which has ASCII code 2.

Code to Print All ASCII codes

The following code prints any ASCII character in the usual form: the char itself for printable characters, and the two-char sequence for the control characters:

/*
 * print printable chars as themselves and control chars with a leading ^
 */
void print_char(char c)
{
    if ( c == 127 )                /* special case for DEL */
        printf("^?");
    else if ( iscntrl(c) )
        printf("^%c", c - 1 + 'A');
    else
        putchar(c);
}
This algorithm takes advantage of the fact that the ASCII codes for 'A' through 'Z' are 65 through 90 consecutively and that the ASCII codes for ^A through ^Z are 1 through 26 consecutively. Adding values in the range 1 through 26 to the value -1 + 'A', produces values in the range 'A' though 'Z'.

A different operation produces the same result with no need to handle 127 as a special case:

void print_char(char c)
{
    if ( iscntrl(c) )
        printf("^%c", c ^ '@');  // bitwise xor with 64
    else
        putchar(c);
}
This algorithm flips bits in the ASCII code instead of doing arithmetic. If you are not familiar with XOR, look it up to see how it changes numbers in the range 0 through 31 to values in the range 64 through 95. You need to represent those numbers in binary to see how this works.