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

    Server rack representing connection load balancing with Python SO_REUSEPORT_LB
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    SO_REUSEPORT_LB: Balance Connections Across Workers

    Learn Python SO_REUSEPORT_LB to distribute connections across multiple workers with safe testing, portability, and graceful shutdown.

    Ler mais

    Tempo de leitura: 5 minutos
    11/10/2026
    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