Hard-coded escape codes draw fine in one terminal and garbage in another; tput fixes that by asking the terminal what it supports. This guide covers reading terminal width, moving the cursor, testing capabilities and recovering a broken terminal, in about ten minutes. On this machine, tput on PATH is ncurses 6.6.20251230 from /home/linuxbrew/.linuxbrew/bin/tput; Ubuntu's ncurses-bin package also provides /usr/bin/tput, version 6.4.20240113. The interfaces used here are shared by both.
You need a shell and a terminal session, no elevated privileges. These examples only write terminal output or inspect its description.
Warning: Don't run tput init or tput reset casually. Both change terminal modes, so they belong to a recovery procedure, not an ordinary prompt.
tput normally takes the terminal type from TERM. Start by inspecting the command and the variable:
$ command -v tput
/home/linuxbrew/.linuxbrew/bin/tput
$ printf '%s\n' "$TERM"
dumb
$ tput -V
ncurses 6.6.20251230
Your path and version can differ; that's fine. The useful checkpoint is that command -v found the executable you meant to test, and TERM isn't empty. A missing or unknown terminal type breaks capability queries.
Tip: Don't "fix" an empty TERM by guessing a value in a script. The terminal emulator, SSH session or login environment is supposed to supply it, and papering over the symptom hides the real problem.
To see the description stored in the terminfo database:
$ tput longname
80-column dumb tty$
longname prints no trailing newline, which is why the next prompt lands on the same line. Treat it as a description for humans, not a reliable machine-readable identifier.
Use the cols capability whenever layout depends on the current terminal:
$ columns=$(tput cols) || exit $?
$ printf 'This terminal is %s columns wide.\n' "$columns"
This terminal is 80 columns wide.
Check the exit status before using the captured value. For a numeric capability, ncurses writes the decimal value followed by a newline; an unavailable value comes back as -1. For cols and lines, ncurses checks the terminal database first, then asks the operating system, then falls back to LINES and COLUMNS. Passing -T tells it to ignore those environment overrides:
$ COLUMNS=120 tput cols
120
$ COLUMNS=120 tput -T dumb cols
80
That difference is easy to miss in tests. If your script needs the real window size, use -T "$TERM" or stop exporting test overrides. If you're deliberately testing an environment-supplied size, leave out -T and note why in a comment.
Terminal control sequences aren't universal, so ask terminfo for the operation and send the result with command substitution or printf. This moves the cursor to row 3, column 4, using zero-based coordinates:
$ tput cup 3 4
$ printf '%s\n' 'text at row 3, column 4'
The output is control bytes, so a log won't show a useful transcript. To inspect the bytes without applying them, capture and hex-dump them:
$ tput -T xterm cup 3 4 | od -An -tx1
1b 5b 34 3b 35 48
That exact sequence belongs to the selected terminal type. Don't copy it into a script when tput cup can generate it fresh. Capabilities that take parameters need them as separate arguments, as in tput cup 3 4, not one quoted string.
For a simple screen clear, check the exit status rather than assuming it worked:
if ! tput clear; then
printf '%s\n' 'Cannot clear this terminal' >&2
exit 1
fi
Tip: -x stops tput clear wiping scrollback along with the visible screen, a useful boundary in an interactive tool where the user's scrollback matters:
$ tput -x clear
Some capabilities are booleans, not strings. hc, for example, tests whether the terminal is a hard-copy device:
if tput hc; then
printf '%s\n' 'Use output that does not depend on screen control'
else
printf '%s\n' 'This is not a hard-copy terminal'
fi
A boolean capability prints nothing meaningful; its exit status is the answer. 0 means present, 1 means absent, and the same rule applies to any other boolean code from the terminfo documentation. Don't test whether the command's output is empty: an absent string capability and a present-but-empty-looking one aren't represented the same way.
For ordinary use, these exit statuses matter: 0 means a boolean or string capability is present, or a command succeeded; 1 means a boolean or numeric capability is absent; 2 means a usage error or no terminal type; 3 means an unknown terminal type; 4 means an unknown capability code. Higher values mean a system error. Preserve that distinction in scripts:
if ! width=$(tput cols 2>/tmp/tput-error); then
status=$?
printf 'tput cols failed with status %s: %s\n' "$status" "$(cat /tmp/tput-error)" >&2
exit "$status"
fi
That example writes to a temporary error file and leaves it for inspection; in a longer-lived script, create a private temporary directory and clean it up on exit. Never feed untrusted terminal-type text into a command without treating it as data, and never turn a failed query into a guessed width.
When you need several capabilities at once, -S reads one per input line:
$ tput -S <<'EOF'
clear
cup 3 4
bold
EOF
Only use this with controlled input. Each line can carry a capability plus its parameters. In this mode, status 0 means every operand was understood, status 4 means some weren't. The output is terminal control data, so send it to the terminal, not a text log.
If a program leaves raw mode, echo disabled or a strange character mapping behind, tput reset reinitialises the terminal and restores sane modes. It can affect the terminal even when its own output is redirected, because mode changes are terminal operations, not just standard output:
$ tput reset
Warning: This is not an undo button for arbitrary shell state, and it won't repair a bad TERM value. Run it in the affected terminal itself, never in a service or cron job. If it fails because there's no terminal, reconnect to a real one and diagnose TERM and the terminfo installation there.
TERM the script will actually use.tput cols or tput lines, with the exit status checked.tput reset is reserved for interactive recovery, since it changes terminal modes.