El módulo tty ofrece funciones de conveniencia para colocar un terminal Unix en modo raw o cbreak. Se apoya en termios y evita que la aplicación tenga que modificar manualmente muchas flags para tareas como leer una tecla sin esperar Enter.
Raw y cbreak son útiles en juegos de terminal, menús interactivos, hotkeys, herramientas de administración e interfaces de pantalla completa. También son peligrosos si el estado no se restaura: el shell puede quedar sin echo, sin señales o con teclas aparentemente rotas.
Disponibilidad
tty solo está disponible en Unix porque depende de termios. Windows requiere otra API o una biblioteca multiplataforma.
try:
import tty
except ImportError:
tty = None
El descriptor también debe representar un terminal real. Comprueba os.isatty() antes de cambiar stdin o stdout.
Raw frente a cbreak
En cbreak, los caracteres se entregan inmediatamente y el echo se desactiva, pero parte del procesamiento del terminal continúa. Ctrl+C normalmente genera SIGINT y Enter mantiene el mapeo habitual.
Raw entrega bytes con muy poca transformación. Señales, conversiones de newline, flow control y procesamiento de salida pueden quedar desactivados. Ofrece máximo control, pero obliga a implementar más lógica.
setcbreak()
import os
import sys
import termios
import tty
fd = sys.stdin.fileno()
original = tty.setcbreak(fd)
try:
tecla = os.read(fd, 1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, original)
print(tecla)
Desde Python 3.12, setcbreak() devuelve los atributos originales. Versiones anteriores devolvían None, así que código compatible debe llamar tcgetattr() antes.
setraw()
original = tty.setraw(fd)
try:
datos = os.read(fd, 32)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, original)
Raw desactiva más comportamiento del driver. Úsalo solo cuando la aplicación deba interpretar cada byte, incluso Ctrl+C y secuencias de control.
Restaurar siempre en finally
La restauración debe ejecutarse después de KeyboardInterrupt, errores de parsing, EOF y excepciones inesperadas.
def leer_tecla():
fd = sys.stdin.fileno()
original = tty.setcbreak(fd)
try:
return os.read(fd, 1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, original)
No dependas solo de atexit. No se ejecuta después de SIGKILL o algunos crashes nativos.
El argumento when
setraw() y setcbreak() pasan when a tcsetattr(). El default es TCSAFLUSH, que espera la salida y descarta entrada pendiente.
Usa TCSADRAIN cuando no quieras eliminar teclas previas.
cfmakeraw()
Python 3.12 añade cfmakeraw(mode), que modifica una lista de atributos sin aplicarla.
nuevo = termios.tcgetattr(fd)
tty.cfmakeraw(nuevo)
nuevo[6][termios.VMIN] = 0
nuevo[6][termios.VTIME] = 5
termios.tcsetattr(fd, termios.TCSADRAIN, nuevo)
Permite partir de raw y personalizar timeouts o flags.
cfmakecbreak()
cfmakecbreak(mode) limpia ECHO e ICANON y configura VMIN en uno con VTIME cero.
nuevo = termios.tcgetattr(fd)
tty.cfmakecbreak(nuevo)
termios.tcsetattr(fd, termios.TCSADRAIN, nuevo)
Desde Python 3.12.2 ya no limpia ICRNL. Esto coincide con el comportamiento histórico y con Linux, macOS y BSD.
Compatibilidad de versiones
if hasattr(tty, "cfmakecbreak"):
tty.cfmakecbreak(atributos)
else:
atributos[3] &= ~(termios.ECHO | termios.ICANON)
atributos[6][termios.VMIN] = 1
atributos[6][termios.VTIME] = 0
Conserva ICRNL en el fallback para reproducir el cbreak actual.
Flechas
Una flecha normalmente envía una secuencia que comienza con ESC. Leer un byte no identifica la tecla completa.
primero = os.read(fd, 1)
if primero == b"\x1b":
restante = os.read(fd, 2)
secuencia = primero + restante
Las secuencias cambian por terminal y modificadores. Para una solución robusta, usa curses o terminfo.
Distinguir Escape
Escape aislado comparte el primer byte de muchas secuencias. Usa VMIN/VTIME o select para esperar brevemente bytes adicionales.
Un timeout corto falla por SSH lento; uno largo retrasa Escape.
Unicode
Raw y cbreak entregan bytes. Un carácter Unicode puede necesitar varios bytes UTF-8. Usa decoder incremental.
import codecs
decoder = codecs.getincrementaldecoder("utf-8")()
texto = decoder.decode(bloque)
El parser debe distinguir texto y secuencias de control.
Ctrl+C
En cbreak, ISIG normalmente sigue activo y Ctrl+C genera KeyboardInterrupt. En raw, \x03 puede llegar como entrada común.
Si raw desactiva señales, ofrece una tecla de salida confiable.
Ctrl+Z
En cbreak, Ctrl+Z puede suspender el proceso. Al volver, confirma el tamaño y redibuja. En raw puede ser solo un byte.
Esperar con select()
import select
listos, _, _ = select.select([fd], [], [], 0.5)
if listos:
bloque = os.read(fd, 1024)
else:
ejecutar_tarea_periodica()
select en Python explica readiness e I/O no bloqueante.
Context manager
from contextlib import contextmanager
@contextmanager
def modo_cbreak(fd):
original = tty.setcbreak(fd)
try:
yield
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, original)
Para Python anterior a 3.12, guarda los atributos antes.
stdin o /dev/tty
stdin puede estar redirigido aunque exista un terminal controlador. En Unix, /dev/tty abre ese terminal.
with open("/dev/tty", "rb+", buffering=0) as terminal:
fd = terminal.fileno()
Puede fallar en servicios, jobs en background o containers sin TTY.
Subprocesses
Un hijo iniciado mientras el terminal está raw hereda ese estado. Restaura modo normal antes de ejecutar comandos externos o usa pty.
Threads
El estado pertenece al terminal, no al thread. Dos threads cambiando modos pueden restaurar valores fuera de orden. Centraliza la entrada interactiva.
Salida y cursor
Raw puede desactivar procesamiento de salida. Un \n quizá no vuelva a la primera columna. Usa \r\n o conserva flags de salida si no necesitas raw completo.
Resize
Usa termios.tcgetwinsize() para filas y columnas. Después de SIGWINCH, marca el layout y redibuja en el loop.
Tests con pty
pty crea un pseudo-terminal para pruebas de interfaces interactivas sin alterar el TTY real.
Recuperación
Si un bug deja el shell sin echo, escribe stty sane y pulsa Enter aunque no veas caracteres.
Seguridad
Raw no es ocultación segura de contraseñas. Además, no imprimas secuencias de escape no confiables, porque pueden manipular terminal, título o clipboard.
Pruebas recomendadas
Prueba raw y cbreak, Python 3.11 y 3.12+, Ctrl+C, Ctrl+Z, Escape, flechas, Unicode, stdin redirigido, SSH lento, resize, excepciones, subprocesses y ausencia de TTY.
Errores comunes
Los fallos frecuentes son no restaurar, asumir el retorno nuevo en Python antiguo, confundir raw y cbreak, leer flechas como un byte, ignorar Unicode, descartar entrada sin intención e iniciar subprocesses en el modo equivocado.
Conclusión
tty simplifica raw y cbreak en Unix. Elige cbreak para entrada inmediata conservando señales; usa raw solo cuando necesites controlar cada byte.
Restaura en finally, maneja diferencias de versión y prueba en terminal real y pseudo-terminal. Consulta la documentación oficial de tty y termios en Python.







