ANSI escapes
What ANSI escape sequences are, how terminal control codes work, and practical usage with examples.
topic: Linux
release date: 2026-03-13
introduction
ANSI escape sequences are short byte sequences that instruct a terminal emulator to perform actions such as moving the cursor, changing colors, clearing parts of the screen, or switching modes. They are lightweight control instructions built on top of ASCII and were standardized from the original VT100 and ANSI X3.64 families of control codes. Modern terminal emulators implement most of these sequences and a number of private extensions.
In practice you will see escape sequences in scripts, command output, programs using ncurses, and when manipulating terminals directly from shell. Correct use of escape codes allows creating full-screen text UIs, colored logs, progress indicators, and robust cleanup on exit.
sequence entries
An escape sequence usually starts with the Escape character (decimal 27) followed by a sequence of bytes that indicate the class and arguments. Common ways to represent Escape in source are:
- Ctrl-Key - ^[
- octal - \033
- hex - \x1B
- unicode - \u001b
- decimal - 27
The most common family is the CSI sequences. CSI is the Control Sequence Introducer. It can be formed by ESC [ or a single-byte CSI code. CSI sequences accept optional numeric parameters separated by semicolons and end with a single letter or symbol that defines the action.
basic example
# set bold and red foreground
printf '\x1b[1;31mHello\x1b[0m\n'
This sequence uses CSI [1;31m to set attributes then resets with [0m.
general/basic codes
Before CSI sequences we often rely on basic ASCII control characters for simple behavior. These are important because they are widely portable and often easier to reason about when writing scripts.
| Sequence | Name | Description |
|---|---|---|
\e[0m |
Reset | Reset all text attributes and return to terminal default style. |
\e[1m |
Bold | Enable bold or increased intensity text. |
\e[2m |
Dim | Enable faint or reduced intensity text. |
\e[3m |
Italic | Enable italic text style. Not supported by every terminal. |
\e[4m |
Underline | Enable underline text decoration. |
\e[5m |
Slow blink | Enable slow blinking text. |
\e[6m |
Rapid blink | Enable rapid blinking text. |
\e[7m |
Inverse | Swap foreground and background colors. |
\e[8m |
Hidden | Hide text. Characters exist but are not visible. |
\e[9m |
Strikethrough | Draw a line through the text. |
\e[21m |
Reset bold | Disable bold attribute. |
\e[22m |
Reset bold and dim | Disable both bold and faint text modes. |
\e[23m |
Reset italic | Disable italic text. |
\e[24m |
Reset underline | Disable underline. |
\e[25m |
Reset blink | Disable blinking text. |
\e[27m |
Reset inverse | Restore normal foreground and background colors. |
\e[28m |
Reset hidden | Make hidden text visible again. |
\e[29m |
Reset strikethrough | Disable strikethrough decoration. |
\a |
BEL | Terminal bell sound. |
\b |
Backspace | Move cursor one character backwards. |
\t |
Horizontal tab | Move cursor to the next tab stop. |
\n |
New line | Move cursor to next line. |
\r |
Carriage return | Move cursor to the beginning of the line. |
\v |
Vertical tab | Move cursor vertically to the next line tab stop. |
\f |
Form feed | Advance to the next page or clear the screen on some terminals. |
moving the cursor
Cursor movement sequences let you position output precisely. These are the building blocks for progress bars, interactive prompts, and full-screen text UIs.
| ESC Code Sequence | Description |
|---|---|
| ESC[H | Move cursor to home position (1,1 in many terminals) |
| ESC[{line};{col}H | Move cursor to line, column |
| ESC[#A | Move cursor up # lines |
| ESC[#B | Move cursor down # lines |
| ESC[#C | Move cursor right # columns |
| ESC[#D | Move cursor left # columns |
| ESC[6n | Request cursor position - terminal replies with ESC[row;colR |
| ESC[s | Save cursor position |
| ESC[u | Restore saved cursor position |
| ESC M | Move cursor up one line, scrolling if needed |
example - simple progress spinner
spinner() {
local i=0
local chars='|/-\'
while true; do
printf '\r%c' "${chars:i%4:1}"
i=$((i+1))
sleep 0.1
done
}
# run spinner in background, PID stored so we can kill it later
spinner & sp=$!
sleep 2
kill "$sp"
printf '\r \r' # clear spinner
In the spinner we use carriage return and printing to the same line instead of full cursor moves for simplicity.
line/terminal erasing
Erase sequences let you clear parts of the screen or the current line. They do not implicitly move the cursor unless documented.
| ESC Sequence | Description |
|---|---|
| ESC[J / ESC[0J | Erase from cursor to end of screen |
| ESC[1J | Erase from cursor to start of screen |
| ESC[2J | Erase entire screen |
| ESC[K / ESC[0K | Erase from cursor to end of line |
| ESC[1K | Erase from start of line to cursor (inclusive) |
| ESC[2K | Erase entire line |
example - progress with line erase
for i in {1..40}; do
printf '\r%s' "progress $i/40"
sleep 0.05
done
printf '\r\x1b[2KDone\n'
basic 8-16 colors
Most terminals support a basic palette of 8 colors and often an extended set of 16 colors which include a bright variant. Colors are controlled by SGR parameters within CSI sequences. Multiple attributes can be combined with semicolons.
| Color name | foreground | background |
|---|---|---|
| Black | 30 | 40 |
| Red | 31 | 41 |
| Green | 32 | 42 |
| Yellow | 33 | 43 |
| Blue | 34 | 44 |
| Magenta | 35 | 45 |
| Cyan | 36 | 46 |
| White | 37 | 47 |
Bright colors use 90 to 97 for foregrounds and 100 to 107 for backgrounds. Alternatively, some terminals map bold to bright colors when available.
examples
# bold red text
printf '\x1b[1;31mBold red\x1b[0m\n'
# bright green foreground (if supported)
printf '\x1b[92mBright green\x1b[0m\n'
# dim white on red background
printf '\x1b[2;37;41mDim white on red\x1b[0m\n'
256 colors
256-color mode extends the palette via indexed colors. Use SGR parameters 38;5;{ID}m for foreground and 48;5;{ID}m for background. IDs range 0 to 255.
Below is an illustration of the 256 color palette for reference.
example
# orange-ish from 256 palette (example ID 208)
printf '\x1b[38;5;208mOrange text\x1b[0m\n'
# background with ID 236 (dark gray)
printf '\x1b[48;5;236m \x1b[0m\n'
RGB true colors
Truecolor mode allows precise 24-bit colors using the sequences 38;2;r;g;b for foreground and 48;2;r;g;b for background. Most modern terminal emulators support truecolor.
example
# bright orange via RGB
printf '\x1b[38;2;255;165;0mTruecolor orange\x1b[0m\n'
# subtle background
printf '\x1b[48;2;20;20;20m \x1b[0m\n'
screen modes
Screen mode sequences change terminal modes such as switching to an alternate buffer, enabling application cursor keys, or switching line wrapping. These are often implemented as DEC private modes using sequences that begin with ESC[?.
| Sequence | Description |
|---|---|
| ESC[?1049h | Enable alternate screen buffer and save cursor |
| ESC[?1049l | Disable alternate screen buffer and restore |
| ESC[?25l | Hide cursor |
| ESC[?25h | Show cursor |
| ESC[?1h | Enable application cursor keys |
| ESC[?1l | Disable application cursor keys |
example - enter alternate buffer and hide cursor
# save state, switch to alternate buffer, hide cursor
printf '\x1b[?1049h\x1b[?25l'
# ... full-screen UI ...
# restore main buffer and show cursor
printf '\x1b[?1049l\x1b[?25h'
private modes
Private modes are vendor extensions that begin with ESC[?. They are widely used but nonstandardized. Many emulators implement a similar set of private modes for convenience.
- ESC[?25l - hide cursor
- ESC[?25h - show cursor
- ESC[?1049h - enable alternate buffer
- ESC[?1049l - disable alternate buffer
- ESC[?2004h - enable bracketed paste
- ESC[?2004l - disable bracketed paste
keyboard strings
Some control sequences let you program keys or request special strings from the terminal. These are less commonly used in modern scripts but are part of termcap/terminfo and older terminal control sets.
Keyboard string syntax is often something like ESC[{code};{string}p where code identifies the key and string defines the new content. Terminfo/termcap provide higher level access to these features when writing portable applications.
| Key | Code | SHIFT+code | CTRL+code | ALT+code |
|---|---|---|---|---|
| F1 | 0;59 | 0;84 | 0;94 | 0;104 |
| F2 | 0;60 | 0;85 | 0;95 | 0;105 |
| F3 | 0;61 | 0;86 | 0;96 | 0;106 |
| F4 | 0;62 | 0;87 | 0;97 | 0;107 |
| F5 | 0;63 | 0;88 | 0;98 | 0;108 |
| F6 | 0;64 | 0;89 | 0;99 | 0;109 |
| F7 | 0;65 | 0;90 | 0;100 | 0;110 |
| F8 | 0;66 | 0;91 | 0;101 | 0;111 |
| F9 | 0;67 | 0;92 | 0;102 | 0;112 |
| F10 | 0;68 | 0;93 | 0;103 | 0;113 |
| F11 | 0;133 | 0;135 | 0;137 | 0;139 |
| F12 | 0;134 | 0;136 | 0;138 | 0;140 |
| HOME (num keypad) | 0;71 | 55 | 0;119 | -- |
| UP ARROW (num keypad) | 0;72 | 56 | (0;141) | -- |
| PAGE UP (num keypad) | 0;73 | 57 | 0;132 | -- |
| LEFT ARROW (num keypad) | 0;75 | 52 | 0;115 | -- |
| RIGHT ARROW (num keypad) | 0;77 | 54 | 0;116 | -- |
| END (num keypad) | 0;79 | 49 | 0;117 | -- |
| DOWN ARROW (num keypad) | 0;80 | 50 | (0;145) | -- |
| PAGE DOWN (num keypad) | 0;81 | 51 | 0;118 | -- |
| INSERT (num keypad) | 0;82 | 48 | (0;146) | -- |
| DELETE (num keypad) | 0;83 | 46 | (0;147) | -- |
| HOME | (224;71) | (224;71) | (224;119) | (224;151) |
| UP ARROW | (224;72) | (224;72) | (224;141) | (224;152) |
| PAGE UP | (224;73) | (224;73) | (224;132) | (224;153) |
| LEFT ARROW | (224;75) | (224;75) | (224;115) | (224;155) |
| RIGHT ARROW | (224;77) | (224;77) | (224;116) | (224;157) |
| END | (224;79) | (224;79) | (224;117) | (224;159) |
| DOWN ARROW | (224;80) | (224;80) | (224;145) | (224;154) |
| PAGE DOWN | (224;81) | (224;81) | (224;118) | (224;161) |
| INSERT | (224;82) | (224;82) | (224;146) | (224;162) |
| DELETE | (224;83) | (224;83) | (224;147) | (224;163) |
| PRINT SCREEN | -- | -- | 0;114 | -- |
| PAUSE/BREAK | -- | -- | 0;0 | -- |
| BACKSPACE | 8 | 8 | 127 | (0) |
| ENTER | 13 | -- | 10 | (0 |
| TAB | 9 | 0;15 | (0;148) | (0;165) |
| NULL | 0;3 | -- | -- | -- |
| A | 97 | 65 | 1 | 0;30 |
| B | 98 | 66 | 2 | 0;48 |
| C | 99 | 67 | 3 | 0;46 |
| D | 100 | 68 | 4 | 0;32 |
| E | 101 | 69 | 5 | 0;18 |
| F | 102 | 70 | 6 | 0;33 |
| G | 103 | 71 | 7 | 0;34 |
| H | 104 | 72 | 8 | 0;35 |
| I | 105 | 73 | 9 | 0;23 |
| J | 106 | 74 | 10 | 0;36 |
| K | 107 | 75 | 11 | 0;37 |
| L | 108 | 76 | 12 | 0;38 |
| M | 109 | 77 | 13 | 0;50 |
| N | 110 | 78 | 14 | 0;49 |
| O | 111 | 79 | 15 | 0;24 |
| P | 112 | 80 | 16 | 0;25 |
| Q | 113 | 81 | 17 | 0;16 |
| R | 114 | 82 | 18 | 0;19 |
| S | 115 | 83 | 19 | 0;31 |
| T | 116 | 84 | 20 | 0;20 |
| U | 117 | 85 | 21 | 0;22 |
| V | 118 | 86 | 22 | 0;47 |
| W | 119 | 87 | 23 | 0;17 |
| X | 120 | 88 | 24 | 0;45 |
| Y | 121 | 89 | 25 | 0;21 |
| Z | 122 | 90 | 26 | 0;44 |
| 1 | 49 | 33 | -- | 0;120 |
| 2 | 50 | 64 | 0 | 0;121 |
| 3 | 51 | 35 | -- | 0;122 |
| 4 | 52 | 36 | -- | 0;123 |
| 5 | 53 | 37 | -- | 0;124 |
| 6 | 54 | 94 | 30 | 0;125 |
| 7 | 55 | 38 | -- | 0;126 |
| 8 | 56 | 42 | -- | 0;126 |
| 9 | 57 | 40 | -- | 0;127 |
| 0 | 48 | 41 | -- | 0;129 |
| - | 45 | 95 | 31 | 0;130 |
| = | 61 | 43 | --- | 0;131 |
| [ | 91 | 123 | 27 | 0;26 |
| ] | 93 | 125 | 29 | 0;27 |
| \ | 92 | 124 | 28 | 0;43 |
| ; | 59 | 58 | -- | 0;39 |
| ' | 39 | 34 | -- | 0;40 |
| , | 44 | 60 | -- | 0;51 |
| . | 46 | 62 | -- | 0;52 |
| / | 47 | 63 | -- | 0;53 |
| ` | 96 | 126 | -- | (0;41) |
| ENTER (keypad) | 13 | -- | 10 | (0;166) |
| / (keypad) | 47 | 47 | (0;142) | (0;74) |
| * (keypad) | 42 | (0;144) | (0;78) | -- |
| - (keypad) | 45 | 45 | (0;149) | (0;164) |
| + (keypad) | 43 | 43 | (0;150) | (0;55) |
| 5 (keypad) | (0;76) | 53 | (0;143) | -- |
usage and examples
Here are practical, battle-tested patterns mixing raw escape sequences and small shell snippets. Each example has a short explanation, then a code wrapper with either raw escapes or a tiny script. The goal is: readable, copy/pasteable, and predictable. Test on the target terminal (xterm, kitty, alacritty, gnome-terminal, Termux, tmux etc.) before deploying.
Note - I use \e in human-friendly examples and \x1b in scripts where portability matters. Both represent the Escape (27) character.
1) Moving up and clearing lines - in-place multi-line updates
Common trick to update N previous lines: move cursor up N lines, go to start of line, erase line or erase to end of screen, then print new content. Useful for progress that writes several lines or status blocks.
# raw sequence example - move up one line, return start, erase to end of screen
printf '\e[1A\r\e[J'
Explanation:
- \e[1A - move cursor up 1 line
- \r - carriage return (start of current line)
- \e[J - erase from cursor to end of screen
2) Multi-line status block - update previous 3 lines atomically
Write a status block and later overwrite it by saving the cursor after writing and restoring + rewriting when needed.
# minimal pattern - write 3-line block and update it later
printf '\e[s' # save cursor (position after the block)
printf 'job: running\n'
printf 'progress: 10%\n'
printf 'eta: 00:10\n'
# ... later update: restore cursor, move up 3 lines, rewrite block, restore end
printf '\e[u' # restore saved position (cursor at end of block)
printf '\e[3A' # move up 3 lines
printf '\r\e[2Kjob: running\n' # rewrite line1 (erase whole line then print)
printf '\r\e[2Kprogress: 42%%\n'
printf '\r\e[2Keta: 00:06\n'
printf '\e[s' # re-save new end position
Notes:
- Use \e[2K to erase the whole current line before printing new content.
- Saving cursor with \e[s and restoring with \e[u is handy but be careful when nested or when other code may change the saved stack.
3) Spinner that hides cursor and restores it on exit
Hide the cursor while spinner is running and ensure the cursor is restored on any exit using a trap.
#!/usr/bin/env bash
trap 'printf "\e[?25h\e[0m\n"; exit' EXIT
printf '\e[?25l' # hide cursor
chars='|/-\'
i=0
while true; do
printf '\r%s' "${chars:i%4:1}"
i=$((i+1))
sleep 0.08
done
Key points:
- \e[?25l hides cursor; \e[?25h shows it.
- Trap EXIT ensures cursor is restored no matter what.
4) Clear screen but keep cursor at top-left
Two variants - clear everything and home, or clear region below cursor.
# clear whole screen and move to home
printf '\e[2J\e[H'
# clear from cursor to end of screen only
printf '\e[J'
5) Move relative and reposition - single-line rewrite
Useful when you want to update part of the line: move left n columns and overwrite part of it.
# print then overwrite last 10 chars
printf 'Download: [##########]\n'
sleep 1
printf '\e[1A' # move up 1
printf '\r' # go to start of line
printf 'Download: [####......]\n'
6) Raw escape combos - compact snippets and what they do
Below are small raw examples - each line is a single atomic action you can copy and test. I give the raw escape then a short explanation.
# raw snippet: move up 1, go to start, erase to end of screen
\e[1A\r\e[J
# raw snippet: move cursor to row 10 col 5
\e[10;5H
# raw snippet: save cursor, hide cursor, switch to alt buffer
\e[s\e[?25l\e[?1049h
# raw snippet: restore main buffer, show cursor, restore cursor pos
\e[?1049l\e[?25h\e[u
# raw snippet: set bright cyan foreground, dim, on red background
\e[2;96;41m
# raw snippet: 256-color foreground id 208
\e[38;5;208m
# raw snippet: truecolor foreground orange
\e[38;2;255;165;0m
Short notes:
- \e[10;5H - absolute positioning (line 10, column 5). Many terminals count from 1.
- Switching to alternate buffer \e[?1049h is what full-screen programs do. When you exit they restore main buffer.
- Use \e[0m to reset attributes after color changes.
7) Progress bar with color gradient (256 -> fallback)
Attempt true gradient using 256 colors. If 256-color support is missing, fallback to simple colors. This snippet is self-contained and prints a one-line bar.
#!/usr/bin/env bash
cols=$(tput cols 2>/dev/null || echo 80)
width=$((cols-20))
for i in $(seq 0 $width); do
id=$((16 + (i * 215 / width))) # pick across 16..230 roughly
printf '\r['
printf '\e[48;5;%sm' "$id"
for j in $(seq 0 $i); do printf ' '; done
printf '\e[0m'
for j in $(seq $((i+1)) $width); do printf ' '; done
printf '] %3d%%' "$((i*100/width))"
sleep 0.01
done
printf '\n'
Notes:
- Uses 256-color background (48;5;{id}m) to draw the bar.
- Ensure tput exists or fallback to a default width.
8) Request cursor position and parse robustly (tty-aware)
This version checks for a tty and uses a short timeout to avoid blocking when stdin/out are redirected.
#!/usr/bin/env bash
if [ ! -t 1 ]; then
echo "not a tty"
exit 1
fi
exec 3>&1 4<&0
# ask for cursor position
printf '\e[6n' > /dev/tty
# read response with small timeout
IFS=';' read -r -t 0.2 -d R -a pos < /dev/tty || { echo "no reply"; exit 2; }
row=${pos[0]#*[}
col=${pos[1]}
echo "row=$row col=$col"
# restore fds
exec 3>&- 4<&-
Caveats:
- If the program is not attached to a tty (for example when piped), this will fail or block. Use checks and timeouts.
- Different emulators may reply slightly differently; parsing must be defensive.
9) Bracketed paste - enable and handle pasted text safely
Bracketed paste tells the terminal to wrap pasted text between paste start and end markers, so the application can treat pasted input differently from typed input.
# enable bracketed paste
printf '\e[?2004h'
# pasted text will be wrapped:
# \e[200~pasted text\e[201~`
# disable bracketed paste
printf '\e[?2004l'
Implementation tip:
- Read input and detect sequences \e[200~ and \e[201~ to handle paste blocks specially (strip newlines, avoid accidental shortcuts).
10) Quick cheatsheet of tiny useful raw sequences
Copy these to remember common micro-actions.
\e[0m # reset attributes
\e[1m # bold
\e[2m # dim
\e[3m # italic
\e[4m # underline
\e[7m # inverse
\e[8m # hidden
\e[38;5;196m # set foreground to 256-color id 196
\e[48;2;10;10;10m # set background to RGB(10,10,10)
\e[6n # request cursor pos (reply ESC[row;colR)
\e[s\e[u # save and restore cursor
Do not embed uncontrolled user input into escape sequences. If you must, sanitize numeric arguments and avoid letting users inject arbitrary bytes that could be interpreted as control sequences.
about tput
tput is a utility that queries the terminfo database and outputs terminal control sequences in a portable way. It is useful when you want to avoid hardcoding escape sequences and prefer terminfo capability names.
Unlike raw escape sequences, tput uses the terminal description to select the correct sequence for the current terminal type. This makes scripts using tput more portable across different environments.
basic tput examples
# move cursor to row 10 col 5
tput cup 9 4
# clear the screen
tput clear
# hide and show cursor
tput civis
tput cnorm
# set bold
tput bold
# reset attributes
tput sgr0
tput can also output capability values like number of columns:
cols=$(tput cols)
lines=$(tput lines)
echo "size: $cols x $lines"
outro
ANSI escapes give you precise control over terminal behavior. Start with simple sequences for color and cursor movement, use terminfo and tput for portability, and rely on 256-color or truecolor only when you know the target environment supports it. Always restore terminal state on exit: show cursor, reset attributes, and restore buffer when appropriate.
If this article helps, consider a small donation via >link<.