Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

get the terminal's size

467 views
Skip to first unread message

Alex Ternaute

unread,
Jan 14, 2019, 6:57:45 AM1/14/19
to
Hi there,

I want to know the number of columns of the terminal where python2 writes
it's outputs.

In a terminal, I type
$ echo $COLUMNS
100

But in Python, os.getenv("COLUMNS") gets nothing.
It gets nothing as well if I try to read the output of "echo $COLUMNS"
from a subprocess.

I feel that I'm missing something but what ?

Looking on the internet for a hint, I see that python3 has an
os.get_terminal_size().
Please, is there something similar for python2 ?

Cheers
--
Alex

Peter Otten

unread,
Jan 14, 2019, 7:33:41 AM1/14/19
to
Alex Ternaute wrote:

> Hi there,
>
> I want to know the number of columns of the terminal where python2 writes
> it's outputs.
>
> In a terminal, I type
> $ echo $COLUMNS
> 100
>
> But in Python, os.getenv("COLUMNS") gets nothing.
> It gets nothing as well if I try to read the output of "echo $COLUMNS"
> from a subprocess.
>
> I feel that I'm missing something but what ?

$ python -c 'import os; print os.environ["COLUMNS"]'
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/lib/python2.7/UserDict.py", line 23, in __getitem__
raise KeyError(key)
KeyError: 'COLUMNS'
$ export COLUMNS
$ python -c 'import os; print os.environ["COLUMNS"]'
157

If you see similar output consider adding

export COLUMNS LINES

to your .bashrc or equivalent.

Alex Ternaute

unread,
Jan 14, 2019, 8:11:33 AM1/14/19
to
Hi,

Peter Otten :

>> In a terminal, I type $ echo $COLUMNS 100
>> But in Python, os.getenv("COLUMNS") gets nothing.
>> I feel that I'm missing something but what ?

> $ export COLUMNS

Thank you very much !
--
Aelx

Thomas Jollans

unread,
Jan 14, 2019, 8:27:36 AM1/14/19
to
On 14/01/2019 12.57, Alex Ternaute wrote:
> Hi there,
>
> I want to know the number of columns of the terminal where python2 writes
> it's outputs.
>
> In a terminal, I type
> $ echo $COLUMNS
> 100
>
> But in Python, os.getenv("COLUMNS") gets nothing.
> It gets nothing as well if I try to read the output of "echo $COLUMNS"
> from a subprocess.
>
> I feel that I'm missing something but what ?
>
> Looking on the internet for a hint, I see that python3 has an
> os.get_terminal_size().

Use that then.

> Please, is there something similar for python2 ?

I suspect there is some solution in the curses module...

Grant Edwards

unread,
Jan 14, 2019, 10:22:28 AM1/14/19
to
On 2019-01-14, Peter Otten <__pet...@web.de> wrote:
>
>> I want to know the number of columns of the terminal where python2 writes
>> it's outputs.
>>
>> In a terminal, I type
>> $ echo $COLUMNS
>> 100
>>
>> But in Python, os.getenv("COLUMNS") gets nothing.
>> It gets nothing as well if I try to read the output of "echo $COLUMNS"
>> from a subprocess.
[...]
> If you see similar output consider adding
>
> export COLUMNS LINES
>
> to your .bashrc or equivalent.

That will tell you the terminal size at the time Python was started.

If the terminal size has changed while Python was running, those
environment variables will be wrong. You need to use the TIOCGWINSZ
ioctl call:

http://www.delorie.com/djgpp/doc/libc/libc_495.html

And to detect the size changes (so you know _when_ you need to do the
above), you need to attach a signal handler for the WINCH signal.

--
Grant Edwards grant.b.edwards Yow! Should I do my BOBBIE
at VINTON medley?
gmail.com

Peter Otten

unread,
Jan 14, 2019, 11:52:33 AM1/14/19
to
Grant Edwards wrote:

os.environ["COLUMNS"]

> [...] will tell you the terminal size at the time Python was started.

I admit that none of my scripts is ambitious enough to try and track changes
in terminal size.

But still, Grant's post prompted me to reread the doc and source of
shutil.get_terminal_size(), and I think I should warn you that the
environment variables take precedence over ioctl()-based detection in
os.get_terminal_size().

I'll keep the exports for now because even for a pipe

$ python3 -c 'import shutil; print(shutil.get_terminal_size())' | cat
os.terminal_size(columns=80, lines=24)
$ export LINES COLUMNS
$ python3 -c 'import shutil; print(shutil.get_terminal_size())' | cat
os.terminal_size(columns=137, lines=42)

the current terminal is usually my final target.

Alex Ternaute

unread,
Jan 14, 2019, 12:16:36 PM1/14/19
to
Hi Thomas

>> Looking on the internet for a hint, I see that python3 has an
>> os.get_terminal_size().
> Use that then.

Up to now I wanted to keep compatibility with a big bunch of code in
Python2 that I do no maintain by myself.

Well, I saw that get_terminal_size() follows the windows resizings.
Whenever I consider forking to Python3, this would be my 1st step.

>> Please, is there something similar for python2 ?
> I suspect there is some solution in the curses module...

I did dir(curse) but I could not see if something goes this way.

Bye
--
Alex

Grant Edwards

unread,
Jan 14, 2019, 12:18:08 PM1/14/19
to
On 2019-01-14, Peter Otten <__pet...@web.de> wrote:
> Grant Edwards wrote:
>
> os.environ["COLUMNS"]
>
>> [...] will tell you the terminal size at the time Python was started.
>
> I admit that none of my scripts is ambitious enough to try and track
> changes in terminal size.
>
> But still, Grant's post prompted me to reread the doc and source of
> shutil.get_terminal_size(), and I think I should warn you that the
> environment variables take precedence over ioctl()-based detection
> in os.get_terminal_size().

For non-interactive programs, the environment variable approach is
usually good enough. For interactive programs you use ncurses, slang,
newt, or somesuch library, and they deal with tracking the window size
for you (mostly).

--
Grant Edwards grant.b.edwards Yow! My haircut is totally
at traditional!
gmail.com

Alex Ternaute

unread,
Jan 14, 2019, 12:24:54 PM1/14/19
to
Hi,

Grant Edwards :

>>export COLUMNS LINES
> That will tell you the terminal size at the time Python was started.

Ok, I think tracking these changes in real time is not worth the work to
be done using Python2.

I think at last I'll rewrite this (little) programe in Python3 in order to
use get_terminal_size().

Bye
--
Alex


Schachner, Joseph

unread,
Jan 14, 2019, 12:25:30 PM1/14/19
to
Note sure why you couldn't capture $ echo $COLUMNS from a subprocess call. But, how about this (found on the web):

from win32api import GetSystemMetrics

print "Width =", GetSystemMetrics(0)
print "Height =", GetSystemMetrics(1)

Grant Edwards

unread,
Jan 14, 2019, 12:31:29 PM1/14/19
to
On 2019-01-14, Schachner, Joseph <Joseph.S...@Teledyne.com> wrote:

> Note sure why you couldn't capture $ echo $COLUMNS from a subprocess
> call.

You can. But, the subprocess is going to inherit the value from the
Python program's environment, so it's just pointless complexity.

--
Grant Edwards grant.b.edwards Yow! I represent a
at sardine!!
gmail.com

Schachner, Joseph

unread,
Jan 14, 2019, 12:38:24 PM1/14/19
to
I just tested the fix I proposed, in Python 2.7.13

Code:
from win32api import GetSystemMetrics

def main():
print "Width =", GetSystemMetrics(0)
print "Height =", GetSystemMetrics(1)

if __name__ == '__main__':
main()

Result:
Width = 1536
Height = 864

-----Original Message-----
From: Alex Ternaute <al...@lussinan.invalid>
Sent: Monday, January 14, 2019 6:58 AM
To: pytho...@python.org
Subject: get the terminal's size

Bob van der Poel

unread,
Jan 14, 2019, 1:09:10 PM1/14/19
to

Cameron Simpson

unread,
Jan 14, 2019, 5:44:06 PM1/14/19
to
My cs.tty module (on PyPI) has a ttysize function:

https://pypi.org/project/cs.tty/

which just parses the output of the stty command.

Personally I resist using the environment variables; they're (a) not
exports by default because they're "live" and (b) then don't track
changes if they are exported and (c) rely on the shell providing them. I
just don't trust them.

If you don't want the cs.tty module, the ttysize code is just this:

WinSize = namedtuple('WinSize', 'rows columns')

def ttysize(fd):
''' Return a (rows, columns) tuple for the specified file descriptor.

If the window size cannot be determined, None will be returned
for either or both of rows and columns.

This function relies on the UNIX `stty` command.
'''
if not isinstance(fd, int):
fd = fd.fileno()
P = Popen(['stty', '-a'], stdin=fd, stdout=PIPE, universal_newlines=True)
stty = P.stdout.read()
xit = P.wait()
if xit != 0:
return None
m = re.compile(r' rows (\d+); columns (\d+)').search(stty)
if m:
rows, columns = int(m.group(1)), int(m.group(2))
else:
m = re.compile(r' (\d+) rows; (\d+) columns').search(stty)
if m:
rows, columns = int(m.group(1)), int(m.group(2))
else:
rows, columns = None, None
return WinSize(rows, columns)

Hope this helps.

Cheers,
Cameron Simpson <c...@cskk.id.au>

John Doe

unread,
Jan 14, 2019, 6:04:05 PM1/14/19
to

eryk sun

unread,
Jan 14, 2019, 9:35:31 PM1/14/19
to
On 1/14/19, Schachner, Joseph <Joseph.S...@teledyne.com> wrote:
> I just tested the fix I proposed, in Python 2.7.13
>
> Code:
> from win32api import GetSystemMetrics
>
> def main():
> print "Width =", GetSystemMetrics(0)
> print "Height =", GetSystemMetrics(1)

That gets the monitor size, i.e:

SM_CXSCREEN (0)
The width of the screen of the primary display monitor, in pixels.

SM_CYSCREEN (1)
The height of the screen of the primary display monitor, in pixels.

The console's visible window is a rectangular view on its active
screen buffer. We have to query the screen-buffer information to
obtain the coordinates of this rectangle (right, left, bottom, top).
Python's os.get_terminal_size does this, but I dislike the fact that
it uses the process standard handles instead of C file descriptors and
only supports 0, 1, and 2.

Here's a version that defaults to opening the console's active screen
buffer, "CONOUT$". Otherwise it uses msvcrt.get_osfhandle to get the
handle for the fd argument instead of hard-mapping 0, 1, and 2 to the
process standard handles. For symmetry, I've also added POSIX code
that switches the default to opening "/dev/tty" instead of using
stdout.

import os
import ctypes
from collections import namedtuple

terminal_size = namedtuple('terminal_size', 'columns lines')

if os.name == 'posix':

import tty
import fcntl

class winsize(ctypes.Structure):
_fields_ = (('ws_row', ctypes.c_ushort),
('ws_col', ctypes.c_ushort),
('ws_xpixel', ctypes.c_ushort),
('ws_ypixel', ctypes.c_ushort))

def get_terminal_size(fd=None):
"""Return the size of the terminal window as (columns, lines).

The optional argument fd specifies which file descriptor should
be queried. An OSError is raised if the file descriptor is not
connected to a terminal (Unix) or a console screen buffer
(Windows). The default behavior is to open and query the process
controlling terminal (i.e. Unix "/dev/tty") or active console
screen buffer (i.e. Windows "CONOUT$").
"""
w = winsize()
if fd is None:
fd_used = os.open('/dev/tty', os.O_RDWR)
else:
fd_used = fd
try:
fcntl.ioctl(fd_used, tty.TIOCGWINSZ, w)
finally:
if fd is None:
os.close(fd_used)
return terminal_size(w.ws_col, w.ws_row)

elif os.name == 'nt':

import msvcrt
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
_fields_ = (('dwSize', wintypes._COORD),
('dwCursorPosition', wintypes._COORD),
('wAttributes', wintypes.WORD),
('srWindow', wintypes.SMALL_RECT),
('dwMaximumWindowSize', wintypes._COORD))

kernel32.GetConsoleScreenBufferInfo.argtypes = (
wintypes.HANDLE, ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO))

def get_terminal_size(fd=None):
"""Return the size of the terminal window as (columns, lines).

The optional argument fd specifies which file descriptor should
be queried. An OSError is raised if the file descriptor is not
connected to a terminal (Unix) or a console screen buffer
(Windows). The default behavior is to open and query the process
controlling terminal (i.e. Unix "/dev/tty") or active console
screen buffer (i.e. Windows "CONOUT$").
"""
csbi = CONSOLE_SCREEN_BUFFER_INFO()
w = csbi.srWindow
if fd is None:
fd_used = os.open('CONOUT$', os.O_RDWR)
else:
fd_used = fd
try:
h = msvcrt.get_osfhandle(fd_used)
if not kernel32.GetConsoleScreenBufferInfo(h,
ctypes.byref(csbi)):
raise ctypes.WinError(ctypes.get_last_error())
finally:
if fd is None:
os.close(fd_used)
return terminal_size(w.Right - w.Left + 1, w.Bottom - w.Top + 1)

Alex Ternaute

unread,
Jan 15, 2019, 5:26:58 AM1/15/19
to
Hi Cameron,

> My cs.tty module (on PyPI) has a ttysize function:
> https://pypi.org/project/cs.tty/
> which just parses the output of the stty command.

> If you don't want the cs.tty module, the ttysize code is just this:
>
> WinSize = namedtuple('WinSize', 'rows columns')
>
> def ttysize(fd):
> ''' Return a (rows, columns) tuple for the specified file
> descriptor.
> [...]

Fine, indeed ! I've installed cs.ttyy.

I just don't understand the reason why it takes "fd" as an argument.

I tried : P = Popen(['stty', '-a'], stdout=subprocess.PIPE,
universal_newlines=True) and it runs fine too, so the output seems not
really related to that fd.

Btw I did not know about namedtuple, thanks for that too.

> Hope this helps.
Greatly.

Cheers,
--
Alex

Alex Ternaute

unread,
Jan 15, 2019, 5:43:43 AM1/15/19
to
Hi thereĀ :

> On 2019-01-14, Bob van der Poel <b...@mellowood.ca> wrote:
>> http://stackoverflow.com/questions/566746/how-to-get-console-window-
width-in-python

Simple and direct, I think I'll use this one.
Thanks a lot.

John Doe :
> and have a look at this one too:
> https://stackoverflow.com/questions/1396820/apt-like-column-output-
python-library/1446973#1446973

Fine, but It meets much more needs than my present one.
But thanks too.

Bye
--
Alex

Cameron Simpson

unread,
Jan 15, 2019, 7:01:34 AM1/15/19
to
On 15Jan2019 10:26, Alex Ternaute <al...@lussinan.invalid> wrote:
>> My cs.tty module (on PyPI) has a ttysize function:
>> https://pypi.org/project/cs.tty/
>> which just parses the output of the stty command.
[...]
>Fine, indeed ! I've installed cs.ttyy.
>
>I just don't understand the reason why it takes "fd" as an argument.

"fd" may be a file descriptor or a file (from which it gets the
underlying fd).

>I tried : P = Popen(['stty', '-a'], stdout=subprocess.PIPE,
>universal_newlines=True) and it runs fine too, so the output seems not
>really related to that fd.

But it is! stty(1) fetches the terminal settings from its standard
input, so "fd" is used to supply this. In your Popen test case you
simply don't set the stdin parameter, so it is the Python process'
input. Which is usually what you want. But I want to be able to ask this
of any terminal file, thus the parameter.

Just pass 0 to ttysize, or sys.stdin. Arguably that should be a default
value.

>Btw I did not know about namedtuple, thanks for that too.

Yes, it is very handy.

Cheers,
Cameron Simpson <c...@cskk.id.au>

Alex Ternaute

unread,
Jan 15, 2019, 8:08:49 AM1/15/19
to
Hi Cameron,

>>I tried : P = Popen(['stty', '-a'], stdout=subprocess.PIPE,
>>universal_newlines=True) and it runs fine too, so the output seems not
>>really related to that fd.

> But it is! stty(1) fetches the terminal settings from its standard
> input, so "fd" is used to supply this. In your Popen test case you
> simply don't set the stdin parameter, so it is the Python process'
> input. Which is usually what you want.

> But I want to be able to ask this
> of any terminal file, thus the parameter.

Ah, Ok; smthlike:
cs.tty.ttysize(0)
WinSize(rows=50, columns=100)
anotherTty=open('/dev/pts/3', 'rw')
cs.tty.ttysize(anotherTty)
WinSize(rows=43, columns=199)

It runs :)

I do not need that today but one day orother it could help.

Cheers
--
Alex

Cameron Simpson

unread,
Jan 15, 2019, 4:52:57 PM1/15/19
to
On 15Jan2019 13:08, Alex Ternaute <al...@lussinan.invalid> wrote:
>>>I tried : P = Popen(['stty', '-a'], stdout=subprocess.PIPE,
>>>universal_newlines=True) and it runs fine too, so the output seems not
>>>really related to that fd.
>
>> But it is! stty(1) fetches the terminal settings from its standard
>> input, so "fd" is used to supply this. In your Popen test case you
>> simply don't set the stdin parameter, so it is the Python process'
>> input. Which is usually what you want.
>
>> But I want to be able to ask this
>> of any terminal file, thus the parameter.
>
>Ah, Ok; smthlike:
> cs.tty.ttysize(0)
> WinSize(rows=50, columns=100)
> anotherTty=open('/dev/pts/3', 'rw')
> cs.tty.ttysize(anotherTty)
> WinSize(rows=43, columns=199)
>
>It runs :)

Exactly so.

BTW, you're aware of:

from cs.tty import ttysize
...
ttysize(0)

I presume, and the above is just your testing?

Cheers,
Cameron Simpson <c...@cskk.id.au>

Rick Johnson

unread,
Jan 15, 2019, 5:11:58 PM1/15/19
to
Cameron Simpson wrote:

> BTW, you're aware of:
>
> from cs.tty import ttysize
> ...
> ttysize(0)


Even after all these messages, I still don't cees no damn tee-tees, but... can anyone explain to me why the heck you would import the tee-tees measuring stick, and then call it with a value of zero? I mean -- for guido's sake man! -- do you wanna see them tee-tees, are not? O_O

Karen Shaeffer

unread,
Jan 15, 2019, 11:38:07 PM1/15/19
to
That will tell you the terminal size at the time Python was started.


If the terminal size has changed while Python was running, those

environment variables will be wrong. You need to use the TIOCGWINSZ

ioctl call:


http://www.delorie.com/djgpp/doc/libc/libc_495.html


And to detect the size changes (so you know _when_ you need to do the

above), you need to attach a signal handler for the WINCH signal.


Hi,

I'm running a python 3 interpreter on linux. I'm actually ssh'd into the
terminal

on a headless server. And so my terminal is my local laptop terminal
window, with

the python interpreter running on the remote linux box terminal,
communicating

over an ssh connection.


$ python3

Python 3.6.7 (default, Oct 22 2018, 11:32:17)

[GCC 8.2.0] on linux

Type "help", "copyright", "credits" or "license" for more information.

>>> import shutil

>>> print(f"{shutil.get_terminal_size()}\n")

os.terminal_size(columns=118, lines=63)


>>> print(f"{shutil.get_terminal_size()}\n")

os.terminal_size(columns=133, lines=63)


>>> print(f"{shutil.get_terminal_size()}\n")

os.terminal_size(columns=118, lines=65)


>>> print(f"{shutil.get_terminal_size()}\n")

os.terminal_size(columns=118, lines=63)



With the python interpreter running on the remote terminal, I have resized

the terminal window on my local laptop several times. And each time, the
remote

python interpreter knows about the change, correctly printing the new size.
I

have done nothing with environment variables. I have not used a signal
handler

for the WINCH signal. It just works.


Karen.

Grant Edwards

unread,
Jan 16, 2019, 10:48:33 AM1/16/19
to
On 2019-01-16, Karen Shaeffer <klssh...@gmail.com> wrote:

[fixed quoting and formatting]

>> That will tell you the terminal size at the time Python was started.
>>
>> If the terminal size has changed while Python was running, those
>> environment variables will be wrong. You need to use the TIOCGWINSZ
>> ioctl call:
>>
>> http://www.delorie.com/djgpp/doc/libc/libc_495.html
>>
>> And to detect the size changes (so you know _when_ you need to do the
>> above), you need to attach a signal handler for the WINCH signal.
>
> I'm running a python 3 interpreter on linux. I'm actually ssh'd into
> the terminal on a headless server. [...]
> [...]
> With the python interpreter running on the remote terminal, I have
> resized the terminal window on my local laptop several times. And
> each time, the remote python interpreter knows about the change,
> correctly printing the new size. I have done nothing with
> environment variables. I have not used a signal handler for the
> WINCH signal. It just works.

Yes, we know that works on Python3. The discussion was about what to
do on Python2.

$ python2
Python 2.7.15 (default, Sep 12 2018, 15:19:18)
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import shutil
>>> shutil.get_terminal_size()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'get_terminal_size'
>>>

--
Grant Edwards grant.b.edwards Yow! I'm having a BIG BANG
at THEORY!!
gmail.com

Wildman

unread,
Jan 16, 2019, 9:00:08 PM1/16/19
to
I have used this Python2 code with success in Linux...

#!/usr/bin/env python

import fcntl
import os
import struct
import termios

tty = os.open(os.ctermid(), os.O_RDONLY)
ts = struct.unpack("hh", fcntl.ioctl(tty, termios.TIOCGWINSZ, "1234"))
os.close(tty)
print str(ts[1]) + "x" + str(ts[0])

--
<Wildman> GNU/Linux user #557453
"There are only 10 types of people in the world...
those who understand Binary and those who don't."
-Spike

Akkana Peck

unread,
Jan 16, 2019, 9:25:59 PM1/16/19
to
> On Mon, 14 Jan 2019 11:57:33 +0000, Alex Ternaute wrote:
>
> > Hi there,
> >
> > I want to know the number of columns of the terminal where python2 writes
> > it's outputs.

A couple days late to the party, a discussion of several ways I tried:
http://shallowsky.com/blog/hardware/serial-24-line-terminals.html
and the script I ended up with:
https://github.com/akkana/scripts/blob/master/termsize

(I've only tested these on Linux).

...Akkana
0 new messages