Python termios: Safe Terminal Control

Published on: August 25, 2026
Reading time: 5 minutes
Detailed view of programming code in a dark theme on a computer screen.

The termios module exposes POSIX terminal control on Unix. It can enable or disable echo, switch between canonical line input and character-at-a-time reads, configure serial speeds, manage input and output queues, and query the terminal window size.

These operations change shared state on the terminal associated with a file descriptor. If a program exits without restoring that state, the user’s shell may remain without echo, with disabled control keys, or in an apparently broken mode. Always save the original attributes and restore them in finally.

Availability

termios is available only on Unix systems with POSIX TTY support. Windows uses different APIs, and the descriptor must refer to a real terminal.

import os
import sys

if not os.isatty(sys.stdin.fileno()):
    raise RuntimeError("stdin is not connected to a TTY")

Piped or file-redirected input does not have ordinary terminal attributes. Detect the situation before applying changes.

The tcgetattr() structure

tcgetattr(fd) returns seven elements:

[iflag, oflag, cflag, lflag, ispeed, ospeed, cc]

The values represent input flags, output flags, control flags, local flags, input speed, output speed, and control characters. Interpret them only through symbolic constants from termios.

Save and restore

import sys
import termios

fd = sys.stdin.fileno()
original = termios.tcgetattr(fd)
updated = termios.tcgetattr(fd)

try:
    updated[3] &= ~termios.ECHO
    termios.tcsetattr(fd, termios.TCSADRAIN, updated)
    secret = input("Password: ")
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, original)
    print()

Call tcgetattr() twice or deep-copy the result because the cc list is mutable. A shallow copy can share the nested list.

When changes take effect

TCSANOW applies immediately. TCSADRAIN waits until queued output is transmitted. TCSAFLUSH also discards unread input.

For password prompts, TCSADRAIN avoids cutting output already written. Use TCSAFLUSH only when dropping pending keystrokes is intentional.

Echo

The ECHO bit controls whether typed characters appear.

attributes[3] &= ~termios.ECHO

Disabling echo does not make password collection automatically secure. Privileged processes and the program itself can still access the value. Prefer getpass.getpass() for ordinary passwords.

Canonical mode

With ICANON enabled, input is delivered after Enter and the terminal driver performs basic line editing. Disabling it allows reads before a full line is complete.

attributes[3] &= ~termios.ICANON

Non-canonical mode is useful for games, hotkeys, and full-screen interfaces, but the application must process bytes and escape sequences itself.

VMIN and VTIME

In the cc array, VMIN and VTIME control non-canonical reads.

attributes[6][termios.VMIN] = 1
attributes[6][termios.VTIME] = 0

VMIN 1 and VTIME 0 waits for at least one byte. VMIN 0 with a positive VTIME creates a timeout measured in tenths of a second. POSIX defines several combinations; test the actual target system.

Read one key event

import os
import sys
import termios

fd = sys.stdin.fileno()
original = termios.tcgetattr(fd)
updated = termios.tcgetattr(fd)
updated[3] &= ~(termios.ICANON | termios.ECHO)
updated[6][termios.VMIN] = 1
updated[6][termios.VTIME] = 0

try:
    termios.tcsetattr(fd, termios.TCSADRAIN, updated)
    key = os.read(fd, 1)
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, original)

print(key)

A visible key can generate several bytes. Arrow and function keys normally produce escape sequences.

Unicode input

os.read() returns bytes. A UTF-8 character may require multiple bytes, so one byte is not necessarily one character.

Use an incremental decoder from codecs or parse the terminal protocol. Do not decode every individual byte as if the input were ASCII.

Signals and control keys

The ISIG flag allows characters such as Ctrl+C and Ctrl+Z to generate signals. Removing it makes those bytes ordinary input.

Disabling ISIG can prevent users from interrupting the application. Provide a reliable exit key and restore state after exceptions. See Python signal.

Input mappings

Flags including ICRNL, INLCR, and IGNCR control carriage-return and newline transformations. Changing them affects Enter and serial protocols.

Modify only the required bits and preserve the remaining configuration.

Output processing

OPOST enables output processing. Raw configurations may disable it, which can also change newline behavior and cursor positioning.

Interactive applications should normally use the convenience functions in tty or a full terminal library instead of rebuilding every flag manually.

Hardware control flags

cflag contains character size, parity, stop bits, receiver, and hardware-flow-control settings. These are especially relevant for serial ports.

Driver support varies. Incorrect configuration can produce unreadable data, block communication, or disable flow control.

Baud rate

ispeed and ospeed use constants such as B9600 and B115200.

attributes[4] = termios.B115200
attributes[5] = termios.B115200

Not every speed is available. Production serial applications often benefit from a higher-level library such as pyserial.

Wait for output with tcdrain()

tcdrain(fd) waits until queued output has been transmitted. It is useful before changing line parameters or closing a serial device.

The call can block for a long time when hardware is slow or disconnected. Design cancellation and timeouts around the operation.

Discard queues with tcflush()

termios.tcflush(fd, termios.TCIFLUSH)

TCIFLUSH discards input, TCOFLUSH output, and TCIOFLUSH both. Dropping output can lose commands that the application already accepted.

Flow control

tcflow() suspends or resumes input or output with actions such as TCOOFF and TCOON. Do not confuse this software control with hardware flow control.

Send a break

tcsendbreak(fd, duration) sends a break condition on serial lines. A zero duration means roughly 0.25 to 0.5 seconds under POSIX; nonzero meanings are system-dependent.

Window size

Since Python 3.11, tcgetwinsize() returns rows and columns.

rows, columns = termios.tcgetwinsize(sys.stdout.fileno())
print(rows, columns)

tcsetwinsize() sets the associated size when the platform supports it. This is particularly useful for pseudo-terminal tests.

SIGWINCH

Unix processes normally receive SIGWINCH after terminal resize. Let the handler set a flag and recompute layout in the main loop.

The later guide to curses in this batch covers full-screen resize handling.

A reusable context manager

from contextlib import contextmanager
import termios

@contextmanager
def temporary_attributes(fd, modify):
    original = termios.tcgetattr(fd)
    updated = termios.tcgetattr(fd)
    modify(updated)
    try:
        termios.tcsetattr(fd, termios.TCSADRAIN, updated)
        yield
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, original)

Centralizing restoration reduces missed error paths.

Fork and subprocesses

Child processes can inherit the same terminal and see temporary attributes. Avoid starting unrelated subprocesses while the TTY is modified unless that behavior is part of the protocol.

A child can also change the shared TTY state. Use a pseudo-terminal when an interactive program needs isolation.

Threads

The state belongs to the terminal, not a thread. Two threads changing flags can restore values in the wrong order. Keep terminal control in one thread.

Manual recovery

If an experimental program leaves the terminal without echo, the Unix command stty sane usually restores a reasonable state. Document this recovery command for users.

Testing

Test a real terminal and redirected input, Ctrl+C, exceptions during reads, Unicode, arrows, resize, suspend/resume, subprocesses, serial hardware, VMIN/VTIME combinations, and restoration after failure.

Use pty for automated TTY tests, but still perform manual tests on every supported terminal.

Common mistakes

Common failures include omitting finally, modifying the original list, shallow-copying cc, treating one key as one byte, disabling ISIG without another exit path, operating on redirected stdin, changing too many flags, and failing to test exception restoration.

Conclusion

termios provides precise POSIX terminal and serial control. Use it only when low-level flags are necessary; prefer tty, getpass, curses, or specialized serial libraries for common tasks.

Save and restore state, change only required bits, and test on the target Unix system. Consult the official termios documentation and termios(3).

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Python asynchronous code on a laptop for inspect.markcoroutinefunction
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: Detect Async Wrappers

    Learn inspect.markcoroutinefunction in Python to identify async wrappers, integrate frameworks, and avoid incorrect coroutine detection.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Python code for traversing folders and files with Path.walk
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: Traverse Directories Safely

    Learn Python Path.walk to traverse directories, filter files, handle errors, and control directory-tree processing safely.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Python process debugging in a terminal with source code
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: Debug Running Python Processes

    Learn how to attach pdb to a running Python process, inspect stacks, and diagnose hangs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Python programming code for exact fractions
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: Convert Numbers to Fractions

    Learn Python fractions.from_number for exact rational conversion, float and Decimal handling, approximation, validation, and safe arithmetic.

    Ler mais

    Tempo de leitura: 4 minutos
    09/10/2026
    Developer configuring a Python HTTPS server and TLS certificate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: Build a Local HTTPS Server in Python

    Learn Python HTTPSServer for local HTTPS services, TLS certificates, threaded handling, testing, and practical security limits.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Protected files representing secure TAR extraction with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: Extract TAR Safely

    Learn Python tarfile extraction_filter for safer TAR extraction, path validation, links, permissions, and resource limits.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026