Python tty: Raw and Cbreak Modes

Published on: August 25, 2026
Reading time: 5 minutes
Close-up of hands typing on a laptop keyboard, Python book in sight, coding in progress.

The tty module provides convenient functions for placing a Unix terminal in raw or cbreak mode. It builds on termios and saves applications from manually changing many flags for common tasks such as reading a key without waiting for Enter.

Raw and cbreak modes are useful in terminal games, interactive menus, hotkeys, administration tools, and full-screen interfaces. They are also risky when state is not restored: the user’s shell can remain without echo, signal processing, or normal key behavior.

Availability

tty is Unix-only because it depends on termios. Windows requires another API or a cross-platform library.

try:
    import tty
except ImportError:
    tty = None

The descriptor must also refer to a real terminal. Check os.isatty() before changing standard input or output.

Raw versus cbreak

In cbreak mode, characters are delivered immediately and echo is disabled, while important terminal processing remains active. Ctrl+C normally still generates SIGINT, and Enter retains the platform’s normal carriage-return mapping.

Raw mode delivers bytes with very little transformation. Signals, newline conversion, software flow control, and output processing may be disabled. It offers maximum control but requires much more parsing and cleanup.

setcbreak()

import os
import sys
import termios
import tty

fd = sys.stdin.fileno()
original = tty.setcbreak(fd)
try:
    key = os.read(fd, 1)
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, original)

print(key)

Since Python 3.12, setcbreak() returns the original attributes. Earlier versions returned None, so libraries supporting older Python must call tcgetattr() first.

setraw()

original = tty.setraw(fd)
try:
    data = os.read(fd, 32)
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, original)

Raw mode disables more driver behavior. Use it only when the program must interpret every byte, including Ctrl+C and terminal control sequences.

Always restore in finally

Restoration must run after KeyboardInterrupt, parsing errors, EOF, and unexpected exceptions.

def read_key():
    fd = sys.stdin.fileno()
    original = tty.setcbreak(fd)
    try:
        return os.read(fd, 1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, original)

Do not rely only on atexit. It does not run after SIGKILL or some native crashes.

The when argument

setraw(fd, when=...) and setcbreak() pass when to tcsetattr(). The default is TCSAFLUSH, which waits for output and discards pending input.

Use TCSADRAIN when old keystrokes should not be silently discarded. Choose according to the interaction protocol.

cfmakeraw()

Python 3.12 adds cfmakeraw(mode), which edits an attribute list without applying it immediately.

attributes = termios.tcgetattr(fd)
updated = termios.tcgetattr(fd)
tty.cfmakeraw(updated)
updated[6][termios.VMIN] = 0
updated[6][termios.VTIME] = 5
termios.tcsetattr(fd, termios.TCSADRAIN, updated)

This makes it easy to start from raw mode and customize timeouts or selected flags.

cfmakecbreak()

cfmakecbreak(mode) clears ECHO and ICANON and sets VMIN to one byte with no delay.

updated = termios.tcgetattr(fd)
tty.cfmakecbreak(updated)
termios.tcsetattr(fd, termios.TCSADRAIN, updated)

Since Python 3.12.2, it no longer clears ICRNL. This matches historical behavior and the cbreak mode described by Linux, macOS, and BSD.

Version compatibility

if hasattr(tty, "cfmakecbreak"):
    tty.cfmakecbreak(attributes)
else:
    attributes[3] &= ~(termios.ECHO | termios.ICANON)
    attributes[6][termios.VMIN] = 1
    attributes[6][termios.VTIME] = 0

Preserve ICRNL in fallbacks if you want current cbreak semantics.

Arrow keys

An arrow key normally sends a sequence beginning with ESC. Reading one byte does not identify the full key.

first = os.read(fd, 1)
if first == b"\x1b":
    remaining = os.read(fd, 2)
    sequence = first + remaining

Sequences vary by terminal and modifier keys. Robust applications should use curses or a library backed by terminfo.

Distinguish Escape from a sequence

The Escape key shares its first byte with many control sequences. Use VMIN/VTIME or select to wait briefly for additional bytes.

A timeout that is too short fails over slow SSH connections, while one that is too long makes Escape feel delayed.

Unicode

Raw and cbreak reads return bytes. A Unicode character can use several UTF-8 bytes. Use an incremental decoder instead of decoding every byte separately.

import codecs

decoder = codecs.getincrementaldecoder("utf-8")()
text = decoder.decode(chunk)

The parser must distinguish terminal control sequences from encoded text.

Ctrl+C

In cbreak mode, ISIG normally remains enabled and Ctrl+C raises KeyboardInterrupt. In raw mode, \x03 may arrive as ordinary input.

If raw mode disables signals, provide a dependable exit key. Do not trap users in an interface with no escape path.

Ctrl+Z and suspension

In cbreak mode, Ctrl+Z may suspend the process. After resume, confirm the window size and redraw the interface.

In raw mode, Ctrl+Z may simply be an input byte.

Wait with select()

import select

readable, _, _ = select.select([fd], [], [], 0.5)
if readable:
    chunk = os.read(fd, 1024)
else:
    run_periodic_work()

Python select explains readiness and non-blocking I/O.

A context manager

from contextlib import contextmanager

@contextmanager
def cbreak_mode(fd):
    original = tty.setcbreak(fd)
    try:
        yield
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, original)

For Python before 3.12, save the attributes before calling setcbreak().

stdin, stdout, or /dev/tty?

Standard input may be redirected even when the process has a controlling terminal. On Unix, /dev/tty opens that terminal directly.

with open("/dev/tty", "rb+", buffering=0) as terminal:
    fd = terminal.fileno()

This can fail in services, background jobs, or containers without a TTY. Handle the error.

Subprocesses

A subprocess started while the terminal is raw inherits that state. Ordinary commands may behave unexpectedly.

Restore normal mode temporarily before launching a command or use a pseudo-terminal to isolate the child.

Threads

Terminal state belongs to the device, not a thread. Two threads switching raw and cbreak can restore values out of order. Centralize interactive input in one thread.

Output and cursor behavior

Raw mode may disable output processing. A newline may not return the cursor to the first column. Write \r\n when appropriate or preserve output flags if full raw behavior is unnecessary.

Resize

Use termios.tcgetwinsize() for rows and columns. After SIGWINCH, mark the layout dirty and redraw in the normal event loop.

Testing with pty

The pty module creates a pseudo-terminal for automated interactive tests. It helps verify that code enters and leaves raw or cbreak mode without affecting the developer’s real TTY.

Recovery

If a bug leaves the shell without echo, type stty sane and press Enter even if the characters are invisible.

Security

Raw mode is not secure password masking. The program can still record every byte. Also avoid printing untrusted escape sequences, which can manipulate the terminal, title, or clipboard.

Test raw and cbreak, Python 3.11 and 3.12+, Ctrl+C, Ctrl+Z, Escape, arrows, Unicode, redirected stdin, slow SSH, resize, exceptions, subprocesses, and missing TTY.

Common mistakes

Common failures include not restoring state, assuming setraw() returns attributes on old Python, confusing raw and cbreak, reading arrows as one byte, ignoring Unicode, discarding pending input unintentionally, and launching subprocesses in the wrong mode.

Conclusion

tty simplifies raw and cbreak terminal configuration on Unix. Choose cbreak for immediate keys while retaining signals; use raw only when the application must control every byte and transformation.

Restore in finally, handle Python version differences, and test with both real and pseudo-terminals. Consult the official tty documentation and Python termios.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Detailed view of programming code in a dark theme on a computer screen.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python termios: Safe Terminal Control

    Learn Python termios for canonical mode, echo, key reads, baud rate, queues, window size, and safe restoration of POSIX terminals.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026
    Side view of contemplating female assistant in casual style standing near shelves and choosing file with documents
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python fcntl: File Locks and Control

    Learn Python fcntl for file locks, descriptor flags, ioctl, pipes, and Unix control while avoiding invalid buffers and memory corruption.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python readline: History and Autocomplete

    Learn Python readline for command history, tab completion, line editing, GNU Readline, libedit, and safer interactive terminal prompts.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026
    Peaceful river scene with mossy boulders and flowing water captured in long exposure.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python io: Master Streams and Buffers

    Learn Python io for text and binary streams, buffering, encodings, StringIO, BytesIO, raw I/O, and file-like interfaces.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    Business professional analyzing financial data on multiple computer monitors at his workspace.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python select: Monitor Multiple I/O

    Learn Python select to monitor sockets and pipes, handle partial I/O, backpressure, poll, epoll, signals, and platform differences.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    Striking image of a red-bellied python showcasing its vibrant scales in dramatic lighting.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python signal: Graceful Process Shutdown

    Learn Python signal to handle SIGTERM and SIGINT, stop services, use timers and wakeup file descriptors, and avoid handler deadlocks.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026