os.get_terminal_size
The low-level query: it asks the terminal attached to fd (standard output by default) and raises OSError when output is piped, redirected or running under an IDE. Available on Unix and Windows. Most code should call shutil.get_terminal_size(), which checks COLUMNS/LINES first and falls back to a default instead of raising.
Common call
shutil.get_terminal_size().columns
Returns
os.terminal_size(columns=..., lines=...)
Replaces
parsing the output of stty size or tput cols
Watch out
os.get_terminal_size() raises OSError when stdout is not a terminal
os.get_terminal_size(fdfd — File descriptor to query, 1 (standard output) by default. Positional only.type: int · default: STDOUT_FILENO=STDOUT_FILENO, /)
→ os.terminal_size
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| fd | int | no (STDOUT_FILENO) | File descriptor to query, 1 (standard output) by default. Positional only. |
Return value
os.terminal_size — A tuple subclass (columns, lines).
Common patterns
Wrap text to the terminal width
shutil never raises: it uses COLUMNS, the real terminal, or the fallback.
import shutil, textwrap width = shutil.get_terminal_size(fallback=(80, 24)).columns print(textwrap.fill(text, width=width))
Low-level query with your own fallback
Catch OSError when output may be piped.
import os try: cols, lines = os.get_terminal_size() except OSError: cols, lines = 80, 24
Ask stderr when stdout is redirected
A progress bar on stderr still has a terminal even when stdout goes to a file.
import os, sys cols = os.get_terminal_size(sys.stderr.fileno()).columns
Examples
1. terminal_size is a named 2-tuple
import os
os.terminal_size((80, 24))
Returns
os.terminal_size(columns=80, lines=24)2. Fields by name or position
import os
s = os.terminal_size((80, 24))
(s.columns, s.lines, s[0])
Returns
(80, 24, 80)3. It unpacks like a tuple
import os
cols, rows = os.terminal_size((80, 24))
cols * rows
Returns
19204. A pipe is not a terminal
import os
r, w = os.pipe()
try:
os.get_terminal_size(w)
except OSError as e:
result = type(e).__name__
finally:
os.close(r)
os.close(w)
result
Returns
'OSError'5. shutil reads COLUMNS and LINES first
import os, shutil
saved = {k: os.environ.get(k) for k in ('COLUMNS', 'LINES')}
os.environ['COLUMNS'], os.environ['LINES'] = '120', '40'
try:
size = shutil.get_terminal_size()
finally:
for k, v in saved.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
size
Returns
os.terminal_size(columns=120, lines=40)6. Exactly two values required
import os
os.terminal_size((80,))
Returns
TypeError: os.terminal_size() takes a 2-sequence (1-sequence given)Pitfalls
1. Calling os.get_terminal_size() when output may be piped
Under cron, CI, an IDE console or "python app.py | less" there is no terminal on the fd, and the call raises OSError. Catch it, or use shutil.get_terminal_size().
no fallback
import os r, w = os.pipe() try: size = os.get_terminal_size(w) except OSError as e: size = type(e).__name__ # uncaught, the program would stop here finally: os.close(r) os.close(w) size
'OSError'
catch OSError
import os r, w = os.pipe() try: size = os.get_terminal_size(w) except OSError: size = os.terminal_size((80, 24)) finally: os.close(r) os.close(w) size.columns
80
2. Expecting a width from a non-terminal stream
Code that checks the stream first avoids the exception altogether: isatty() is False for files, pipes and the captured stdout of test runners.
assume a terminal
import io io.StringIO().fileno()
io.UnsupportedOperation: fileno
check isatty()
import io stream = io.StringIO() width = 120 if stream.isatty() else 80 width
80
When to use
Use it
- Formatting tables, progress bars and wrapped text to the terminal width
- Asking a specific fd (e.g. stderr) when you know it is a terminal
Reach for something else
- General scripts → shutil.get_terminal_size(fallback=(80, 24)), which never raises
- Reacting to window resizes → handle SIGWINCH (Unix) and query again
Notes
CPython impl
Unix: the TIOCGWINSZ ioctl on fd (a pipe fails with errno 25, ENOTTY); Windows: the console API on the handle behind fd (a pipe fails with WinError 6). Uncaught, the OSError message therefore differs per platform
Availability
Unix, Windows
shutil
shutil.get_terminal_size(fallback=(80, 24)) uses COLUMNS and LINES if set, then os.get_terminal_size(sys.__stdout__.fileno()), then the fallback; it returns the same os.terminal_size type
FAQ
The size depends on the reader terminal, and when output is captured (as in our checks, a test runner or an IDE) there is no terminal at all. The examples construct terminal_size values and use a pipe to show the OSError path deterministically.