338

Is there a way in python to programmatically determine the width of the console? I mean the number of characters that fits in one line without wrapping, not the pixel width of the window.

Edit

Looking for a solution that works on Linux

anatoly techtonik
20.8k14 gold badges134 silver badges146 bronze badges
asked Feb 19, 2009 at 19:17
2
  • Look this answer for a more extensive solution to have a "columns dependent" printing mechanism. stackoverflow.com/questions/44129613/… Commented May 23, 2017 at 11:29
  • 2
    Please consider changing the accepted answer. The one you've selected is really janky, platform dependent, and it's using os.popen which is deprecated. The top-voted answer showing shutil is the best way. Commented Dec 12, 2020 at 6:37

15 Answers 15

357

Not sure why it is in the module shutil, but it landed there in Python 3.3. See:

Querying the size of the output terminal

>>> import shutil
>>> shutil.get_terminal_size((80, 20)) # pass fallback
os.terminal_size(columns=87, lines=23) # returns a named-tuple

A low-level implementation is in the os module. Cross-platform—works under Linux, Mac OS, and Windows, probably other Unix-likes. There's a backport as well, though no longer relevant.

answered Jan 20, 2013 at 7:25
Sign up to request clarification or add additional context in comments.

13 Comments

That's because you shouldn't be using 2.7 any longer, make the jump to 3.x it's worth it.
@osirisgothra Many hosting providers do not support python3 yet, so some of us are forced to use python2 for back end development. Though that should have nothing to do with getting terminal size...
@osirisgothra Because there's a lot of Python 2 code that would take too much work to port. Also, Pypy still has rather poor Python 3 support.
@whitebeard Is there a reason the subscriber can't install Python 3 on a VPS? Or in 2016, are people still using shared hosting whose administrator is unwilling to install Python 3? For example, WebFaction shared hosting has python3.5 installed.
+1 for a solution that also works when standard input has been redirected from a file! With other solutions, I was either getting Inappropriate ioctl for device errors/warnings, or getting the defined fallback value of 80.
|
282
import os
rows, columns = os.popen('stty size', 'r').read().split()

uses the 'stty size' command which according to a thread on the python mailing list is reasonably universal on linux. It opens the 'stty size' command as a file, 'reads' from it, and uses a simple string split to separate the coordinates.

Unlike the os.environ["COLUMNS"] value (which I can't access in spite of using bash as my standard shell) the data will also be up-to-date whereas I believe the os.environ["COLUMNS"] value would only be valid for the time of the launch of the python interpreter (suppose the user resized the window since then).

(See answer by @GringoSuave on how to do this on python 3.3+)

boxed
4,5063 gold badges29 silver badges32 bronze badges
answered Jun 3, 2009 at 9:59

14 Comments

COLUMNS isn't exported by default in Bash, that's why os.environ["COLUMNS"] doesn't work.
rows, columns = subprocess.check_output(['stty', 'size']).split() is a little shorter, plus subprocess is the future
tput is better than stty, as stty cannot work with PIPE.
rows, columns = subprocess.check_output(['stty', 'size']).decode().split() If you want unicode strings for py2/3 compatibility
Do not use a process (especially from the deprecated os.popen), for this except as a last resort.
|
67

use

import console
(width, height) = console.getTerminalSize()
print "Your terminal's width is: %d" % width

EDIT: oh, I'm sorry. That's not a python standard lib one, here's the source of console.py (I don't know where it's from).

The module seems to work like that: It checks if termcap is available, when yes. It uses that; if no it checks whether the terminal supports a special ioctl call and that does not work, too, it checks for the environment variables some shells export for that. This will probably work on UNIX only.

def getTerminalSize():
 import os
 env = os.environ
 def ioctl_GWINSZ(fd):
 try:
 import fcntl, termios, struct, os
 cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
 '1234'))
 except:
 return
 return cr
 cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
 if not cr:
 try:
 fd = os.open(os.ctermid(), os.O_RDONLY)
 cr = ioctl_GWINSZ(fd)
 os.close(fd)
 except:
 pass
 if not cr:
 cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
 ### Use get(key[, default]) instead of a try/catch
 #try:
 # cr = (env['LINES'], env['COLUMNS'])
 #except:
 # cr = (25, 80)
 return int(cr[1]), int(cr[0])
chown
52.9k17 gold badges138 silver badges170 bronze badges
answered Feb 19, 2009 at 19:18

17 Comments

Thanks for quick reply, but here (effbot.org/zone/console-handbook.htm) it says that "The Console module is currently only available for Windows 95, 98, NT, and 2000." I am looking for a solution that works on Linux. It probably wasn't clear from the tag, I will edit the question accordingly.
since this "console" module you're using is not on the standard python library, you should provide its source code or at least a link to it.
I'm so sorry about that. In fact, I didn't know that module. I tryied import console and it worked, I used console.<tab><tab> and getTerminalSize() showed up. Instead of looking where it's from I already posted an answer because I was so lucky of the simplicity g
I might be looking at a different "console" module, could you please provide a link for the one you have?
Oh, and not to pile on the code, but "cr" is a confusing name because it implies the tuple is (cols, rows). In reality, it is the reverse.
|
62

Code above didn't return correct result on my linux because winsize-struct has 4 unsigned shorts, not 2 signed shorts:

def terminal_size():
 import fcntl, termios, struct
 h, w, hp, wp = struct.unpack('HHHH',
 fcntl.ioctl(0, termios.TIOCGWINSZ,
 struct.pack('HHHH', 0, 0, 0, 0)))
 return w, h

hp and hp should contain pixel width and height, but don't.

answered Jun 9, 2010 at 22:36

7 Comments

This is how it should be done; note that if you intend to print to the terminal, you should use '1' as the file descriptor (first argument of ioctl), as stdin might be a pipe or some different tty.
Perhaps the 0 should be replaced with fcntl.ioctl(sys.stdin.fileno(), ...
this is the best answer - your users will be happy that there's not a surprise subprocess happening just to get term width
this is indeed the cleanest answer. I think you should use stdout or stderr instead of stdin, though. stdin might very well be a pipe. You might also want to add a line such as if not os.isatty(0): return float("inf").
this somehow works on a chromebook terminal which has like no functionality. +1
|
55

It's either:

import os
columns, rows = os.get_terminal_size(0)
# or
import shutil
columns, rows = shutil.get_terminal_size()

The shutil function is just a wrapper around os one that catches some errors and set up a fallback, however it has one huge caveat - it breaks when piping!, which is a pretty huge deal.
To get terminal size when piping use os.get_terminal_size(0) instead.

First argument 0 is an argument indicating that stdin file descriptor should be used instead of default stdout. We want to use stdin because stdout detaches itself when it is being piped which in this case raises an error.

I've tried to figure out when would it makes sense to use stdout instead of stdin argument and have no idea why it's a default here.

jamesdlin
91.2k14 gold badges185 silver badges225 bronze badges
answered Jan 26, 2017 at 0:57

4 Comments

os.get_terminal_size() was introduced in Python 3.3
I tried shutil at first, and it worked the first try. I'm using Python 3.6+.
Using os.get_terminal_size(0) will crash if you pipe to stdin. Try: echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
This answer was given four years earlier. ;-)
40

I searched around and found a solution for windows at :

http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/

and a solution for linux here.

So here is a version which works both on linux, os x and windows/cygwin :

""" getTerminalSize()
 - get width and height of console
 - works on linux,os x,windows,cygwin(windows)
"""
__all__=['getTerminalSize']
def getTerminalSize():
 import platform
 current_os = platform.system()
 tuple_xy=None
 if current_os == 'Windows':
 tuple_xy = _getTerminalSize_windows()
 if tuple_xy is None:
 tuple_xy = _getTerminalSize_tput()
 # needed for window's python in cygwin's xterm!
 if current_os == 'Linux' or current_os == 'Darwin' or current_os.startswith('CYGWIN'):
 tuple_xy = _getTerminalSize_linux()
 if tuple_xy is None:
 print "default"
 tuple_xy = (80, 25) # default value
 return tuple_xy
def _getTerminalSize_windows():
 res=None
 try:
 from ctypes import windll, create_string_buffer
 # stdin handle is -10
 # stdout handle is -11
 # stderr handle is -12
 h = windll.kernel32.GetStdHandle(-12)
 csbi = create_string_buffer(22)
 res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
 except:
 return None
 if res:
 import struct
 (bufx, bufy, curx, cury, wattr,
 left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
 sizex = right - left + 1
 sizey = bottom - top + 1
 return sizex, sizey
 else:
 return None
def _getTerminalSize_tput():
 # get terminal width
 # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window
 try:
 import subprocess
 proc=subprocess.Popen(["tput", "cols"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
 output=proc.communicate(input=None)
 cols=int(output[0])
 proc=subprocess.Popen(["tput", "lines"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
 output=proc.communicate(input=None)
 rows=int(output[0])
 return (cols,rows)
 except:
 return None
def _getTerminalSize_linux():
 def ioctl_GWINSZ(fd):
 try:
 import fcntl, termios, struct, os
 cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'1234'))
 except:
 return None
 return cr
 cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
 if not cr:
 try:
 fd = os.open(os.ctermid(), os.O_RDONLY)
 cr = ioctl_GWINSZ(fd)
 os.close(fd)
 except:
 pass
 if not cr:
 try:
 cr = (env['LINES'], env['COLUMNS'])
 except:
 return None
 return int(cr[1]), int(cr[0])
if __name__ == "__main__":
 sizex,sizey=getTerminalSize()
 print 'width =',sizex,'height =',sizey
answered Jul 1, 2011 at 16:23

1 Comment

You saved me the time of doing this myself. Works on Linux. Should work on Windows as well. Thanks!
26

Starting at Python 3.3 it is straight forward: https://docs.python.org/3/library/os.html#querying-the-size-of-a-terminal

>>> import os
>>> ts = os.get_terminal_size()
>>> ts.lines
24
>>> ts.columns
80
answered Apr 27, 2014 at 23:24

4 Comments

shutil.get_terminal_size() is the high-level function which should normally be used, os.get_terminal_size is the low-level implementation.
This is a near copy of a year-older answer given above.
Any detailed reason, why os.get_terminal_size() not be used?
@KarthikKumar It is stated by the docs here
7

Many of the Python 2 implementations here will fail if there is no controlling terminal when you call this script. You can check sys.stdout.isatty() to determine if this is in fact a terminal, but that will exclude a bunch of cases, so I believe the most pythonic way to figure out the terminal size is to use the builtin curses package.

import curses
w = curses.initscr()
height, width = w.getmaxyx()
answered May 31, 2016 at 22:26

Comments

6

It looks like there are some problems with that code, Johannes:

  • getTerminalSize needs to import os
  • what is env? looks like os.environ.

Also, why switch lines and cols before returning? If TIOCGWINSZ and stty both say lines then cols, I say leave it that way. This confused me for a good 10 minutes before I noticed the inconsistency.

Sridhar, I didn't get that error when I piped output. I'm pretty sure it's being caught properly in the try-except.

pascal, "HHHH" doesn't work on my machine, but "hh" does. I had trouble finding documentation for that function. It looks like it's platform dependent.

chochem, incorporated.

Here's my version:

def getTerminalSize():
 """
 returns (lines:int, cols:int)
 """
 import os, struct
 def ioctl_GWINSZ(fd):
 import fcntl, termios
 return struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234"))
 # try stdin, stdout, stderr
 for fd in (0, 1, 2):
 try:
 return ioctl_GWINSZ(fd)
 except:
 pass
 # try os.ctermid()
 try:
 fd = os.open(os.ctermid(), os.O_RDONLY)
 try:
 return ioctl_GWINSZ(fd)
 finally:
 os.close(fd)
 except:
 pass
 # try `stty size`
 try:
 return tuple(int(x) for x in os.popen("stty size", "r").read().split())
 except:
 pass
 # try environment variables
 try:
 return tuple(int(os.getenv(var)) for var in ("LINES", "COLUMNS"))
 except:
 pass
 # i give up. return default.
 return (25, 80)
answered Jun 16, 2010 at 7:23

1 Comment

I was wandering about the env too, and it is indeed env = os.environ, from accepted answer.
2

Try "blessings"

I was looking for the very same thing. It is very easy to use and offers tools for coloring, styling and positioning in the terminal. What you need is as easy as:

from blessings import Terminal
t = Terminal()
w = t.width
h = t.height

Works like a charm in Linux. (I'm not sure about MacOSX and Windows)

Download and documentation here

or you can install it with pip:

pip install blessings
answered Dec 23, 2014 at 12:00

1 Comment

Nowadays I'd say try "blessed" which is a currently maintained fork (and enhancement) of "blessings".
1

I was trying the solution from here that calls out to stty size:

columns = int(subprocess.check_output(['stty', 'size']).split()[1])

However this failed for me because I was working on a script that expects redirected input on stdin, and stty would complain that "stdin isn't a terminal" in that case.

I was able to make it work like this:

with open('/dev/tty') as tty:
 height, width = subprocess.check_output(['stty', 'size'], stdin=tty).split()
answered Sep 8, 2014 at 18:36

Comments

1

If you're using Python 3.3 or above, I'd recommend the built-in get_terminal_size() as already recommended. However if you are stuck with an older version and want a simple, cross-platform way of doing this, you could use asciimatics. This package supports versions of Python back to 2.7 and uses similar options to those suggested above to get the current terminal/console size.

Simply construct your Screen class and use the dimensions property to get the height and width. This has been proven to work on Linux, OSX and Windows.

Oh - and full disclosure here: I am the author, so please feel free to open a new issue if you have any problems getting this to work.

answered Dec 22, 2015 at 15:58

2 Comments

size = os.get_terminal_size() -> OSError: [Errno 25] Inappropriate ioctl for device
@RuiMartins Please raise the issue on github (with extra details of the system) and I'll take a look.
1

Use subprocess, it is the most convenient way of doing it:

Import:

import subprocess

Example of Use:

print(subprocess.check_output(['stty', 'size']).split())

Note: This function returns bytes but you can cast it to Integer with the int() function.

Note: this function returns an array, being: array[0]=rows and array[1]=columns.

Output:

[b'46', b'188']

For instance if you need to compare whether the width of your console is larger than W, you can do something like this:

if int(subprocess.check_output(['stty', 'size']).split()[1]) > W:
 ...
answered Mar 6, 2023 at 12:59

Comments

0

@reannual's answer works well, but there's an issue with it: os.popen is now deprecated. The subprocess module should be used instead, so here's a version of @reannual's code that uses subprocess and directly answers the question (by giving the column width directly as an int:

import subprocess
columns = int(subprocess.check_output(['stty', 'size']).split()[1])

Tested on OS X 10.9

answered Jul 22, 2014 at 18:30

Comments

-1

Here is an version that should be Linux and Solaris compatible. Based on the posts and commments from madchine. Requires the subprocess module.

def termsize():
 import shlex, subprocess, re
 output = subprocess.check_output(shlex.split('/bin/stty -a'))
 m = re.search('rows\D+(?P\d+); columns\D+(?P\d+);', output)
 if m:
 return m.group('rows'), m.group('columns')
 raise OSError('Bad response: %s' % (output))
>>> termsize()
('40', '100')
answered Apr 19, 2011 at 3:33

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.