views:

3072

answers:

5

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

+7  A: 

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():
    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:
            cr = (25, 80)
    return int(cr[1]), int(cr[0])
Johannes Weiß
Thanks for quick reply, but here (http://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.
nosklo
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*
Johannes Weiß
I might be looking at a different "console" module, could you please provide a link for the one you have?
umnik, the code I posted is the complete console "module". Just put the code in console.py and it will work as in my first sample
Johannes Weiß
Works both Windows and Linux, Thank you!
wow cool, didn't expect that...
Johannes Weiß
Actually, I see on Windows it goes to the exception handler and returns the hardcoded (80,25). Anyways, I was looking for a solution that works on Linux, and it does quite well.
And, this will not work if you `pipe` your Python program output to, say, `less`. You would get the exception: `IOError: [Errno 25] Inappropriate ioctl for device`
Sridhar Ratnakumar
.. to fix that: return 80 if `not sys.stdout.isatty()`
Sridhar Ratnakumar
Hey Johannes, if you incorporate the stty answer below and Sridhar's idea from these comments I'll upvote ;)
Joseph Garvin
+1  A: 

Depending on your shell (I know that bash, at least, does this), you can access the terminal size using the ROWS and COLUMNS environment variables. I don't know of a Windows equivalent.

import os
print os.environ["COLUMNS"]
Ben Blank
It wouldn't work. COLUMNS is not an environment variable, but perhaps a shell variable. See http://mail.python.org/pipermail/python-list/2000-May/033682.html
Sridhar Ratnakumar
@Sridhar — It works for me with bash 2.05, Python 2.2, and Linux 2.4, but it's possible there's something else unusual about my setup (I don't own/administer the box).
Ben Blank
it does work for me, but COLUMNS doesn't change on terminal resize — it's initialized on bash startup
Piotr Findeisen
+12  A: 
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).

madchine
You can get this to work on Solaris as well if instead of "size" you pass "-a". There will be "rows = Y; columns = X" in the semicolon delimited output.
Joseph Garvin
Testing on Ubuntu, it appears -a works on Linux as well but the output is formatted slightly differently -- there's no '=' between rows/columns and the number.
Joseph Garvin
+1  A: 

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.

pascal
+1  A: 

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)
thejoshwolfe