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.

os functionPython 3.3+
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

NameTypeRequiredDescription
fdintno (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
1920
4. 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.