C0 controls — the single-byte control characters
C0 control codes. The 32 single-byte controls below 0x20 — BEL 0x07, BS 0x08, HT 0x09, LF 0x0a, CR 0x0d, ESC 0x1b, SI / SO, and friends. Not escape sequences per se, but every terminal emulator parses them alongside SGR / CSI and they show up in any ANSI-laden log.
1 sequence
When to use C0
Reach for C0 controls when you need a single-byte primitive that needs no \x1b introducer at all — \b to back the cursor up, \t to advance to the next tab stop, \n to feed a line, \r to return to column 1, \a to ring the bell. These are the seven-bit control codes ASCII set aside for terminal control before ANSI / VT existed, and every terminal still acts on them inline. One byte per action (vs three+ for the smallest CSI) — that's the whole reason they're still in your printf format strings. C0 is also the substrate the other families build on: \x1b itself (ESC, 0x1b) is a C0 code, and \a (BEL, 0x07) doubles as the de-facto OSC string terminator.
Common C0 one-liners
Paste any of these into bash / zsh — printf interprets the \x1b byte.
Ring the bell printf '\a'Progress bar (CR + overwrite) printf '\rdone: %d%%\n' 50Tab-aligned two-column table printf 'name\tsize\nbig\t1G\n'
C0 cookbook — four single-byte primitives that don't need ESC
C0 is the family that predates the escape character itself — every code is one byte, no \x1b introducer, and every terminal from a 1970 VT05 to 2026 Ghostty acts on them inline. Four stops cover what you'll actually emit in modern code: the CR / LF pair that ends a line, BS for non-erasing cursor pullback, HT for tab-aligned columns, and BEL for the audible alert that doubles as an OSC string terminator.
1. CR + LF — the line-ending pair
CR (
\r, 0x0d) returns the cursor to column 1 of the current line; LF (\n, 0x0a) drops the cursor down one line. On a Unix tty the kernel'sonlcrmode adds an implicit CR before every emitted LF, which is why a bareprintf '\n'lands at column 1 of the next line in your interactive shell. Turnonlcroff (stty -onlcr) and the same\nproduces a 'staircase' effect — each successive line starts further to the right because nothing reset the column. In alt-screen (\x1b[?1049h) the kernelonlcris bypassed — render loops must emit explicit\r\nbetween rows or the cursor walks off the right. Same on Windows where the console expects CRLF as the literal line terminator.\ralone is the classic progress-bar pattern:printf '\r%d%%' "$pct"repeatedly overwrites the same line.2. BS — back up one column without erasing
Backspace (
\b, 0x08) moves the cursor one column to the left and does not erase the character it lands on. Printabc, emit\b, and the cursor sits on top of thec— a subsequentxoverwrites thecin place. To actually wipe a character the canonical 3-byte dance is\b \b(backspace, space, backspace) — back up, paint a blank, back up again. Modern code more often reaches for\x1b[K(EL — erase line from cursor) instead, but the\b \bpattern is what readline / GNU getline still use for terminal-echo password masking and inline edits. BS is also what the Backspace key normally sends in raw mode — some terminfo entries swap it with DEL (0x7f), and the disagreement between BS and DEL is the perennial source of the 'my Backspace beeps' Ctrl-H confusion.3. HT — horizontal tab and the TBC / HTS lineage
Horizontal Tab (
\t, 0x09) advances the cursor to the next column whose index is a registered tab stop. The default grid is every 8 columns (1, 9, 17, 25, …), soprintf 'a\tb\tc'landsaat col 1,bat col 9,cat col 17. Applications can shift this grid with HTS (\x1bH) — marks the current column as a tab stop — and TBC (\x1b[gclears the stop at the current column;\x1b[3gclears every stop). Together HT + HTS + TBC implement the 1970s typewriter-tab interface that survived into ANSI verbatim. The reverse direction is CBT (\x1b[Z) — back up one tab stop, the Shift-Tab counterpart. Hot tip: HT does not insert space characters into the buffer — it moves the cursor over existing content. If you tab past text and emit a space at the destination, you'll see that space painted, not the underlying content preserved.4. BEL — the alert byte that doubles as the OSC terminator
Bell (
\a, 0x07) was originally the byte that rang the bell of a teletype. Modern terminals turn it into either an audible beep, a visual flash, or a notification badge — controlled by per-app preferences (xterm'sbellIsUrgent, iTerm2's *Profiles → Terminal → Silence Bell*, kitty'senable_audio_bell, etc.). Its second life is much more important: BEL is one of the two standard string terminators for OSC sequences.\x1b]0;title\x07sets the window title; the trailing\x07closes the OSC string just as ST (\x1b\\) would. xterm and every fork accepts BEL-terminated OSC, which is why almost all CLI tools emit it instead of the ECMA-48-correct ST. The cost: if your tool emits a BEL outside an OSC envelope while the user has their terminal set to play the bell, you'll get an unexpected ding. Defensive tip: many emulators (Konsole, gnome-terminal, kitty) suppress BEL inside OSC envelopes specifically — silence in OSC, ring outside — but the legacy Linux console rings either way. If your CLI emits both visible diagnostics and OSC titles, test underxterm -e ./your-cliwith the audible bell enabled to be sure.