Best way of finding terminal width/height?

Grant Edwards grante at visi.com
Wed Feb 8 11:14:34 EST 2006


On 2006-02-08, Joel Hedlund <joel.hedlund at gmail.com> wrote:

>> sys.stdin.read() will return when ... the
>> underyling read() call is aborted by a signal.
>
> Not "return", really?

Yes, really.  On a SIGWINCH, the read() will _either_ return
currently buffered data or thrown an IOError exception.  You
need to handle either one.

> Won't it just pass an exception?

That seems to be the behavior if there is no buffered data to
return. 

> I thought that was what I was catching with the "except
> IOError" part there? I assumed that sys.stdin.read() would
> only return a value properly at EOF?

That assumption seems to be wrong.  sys.stdin.read() also
returns w/o an exception if there's buffered data and a
SIGWINCH occurs.  Whether that is "proper" or not, I don't
know, but that's what it does.

Hmm, here's what the library reference says about file object's
read() method:

  read([size]) 

    Read at most size bytes from the file (less if the read
    hits EOF before obtaining size bytes). If the size argument
    is negative or omitted, read all data until EOF is reached.
    The bytes are returned as a string object. An empty string
    is returned when EOF is encountered immediately. (For
    certain files, like ttys, it makes sense to continue
    reading after an EOF is hit.) Note that this method may
    call the underlying C function fread() more than once in an
    effort to acquire as close to size bytes as possible. Also
    note that when in non-blocking mode, less data than what
    was requested may be returned, even if no size parameter
    was given.

There appear to be a couple problems with this description:

 1) It says that read() in blocking mode without a size
    parameter it will read until EOF.  This is not what happens
    when reading a terminal that receives SIGWINCH, so you're
    right: read() it isn't working as described.

 2) It also says that it makes sense to continue to read a tty
    after you get an EOF.  That's not true.  Once you get an
    EOF on a tty, there's no point in reading it any more:
    you'll continue to get an EOF forever.
    
> It looks to me as if sys.stderr.read() really gets an EOF at
> the final linebreak fed into the terminal prior to window size
> change, because the final unterminated line shows up on my
> shell prompt.

Python's read() (which seems to call fread()) didn't get an EOF
from fread(), it got an error from fread() because the C read()
call that was made by fread() returned an error because it was
interrupted by a signal.

In Python, EOF is when read() returns '' (the empty string).

> Like so:
>
> $ python winch.py
> moo moo
> cow cowmoo moo
> $ cow cow
>
> In this example I type moo moo[ENTER]cow cow on my keyboard
> and then resize the window.
>
> Now that EOF has to come from somewhere

There was no EOF.  read() returned because of the signal, not
because of an EOF.

> (since there's no
> IOError or other exception, or the program wouldn't terminate
> nicely with nothing on stderr) and I'd like to point the blame
> at the terminal. Or is there something really fishy inside
> sys.stdin.read() or signal that puts EOFs into streams?
>
> But anyway, as long as this behavior only shows up on
> interactive operation, the user will likely spot it anyway and
> can react to it. So I think this code would be pretty safe to
> use. What do you think?
>
>> Resign the terminal will abort pending I/O operations on
>> that terminal.  It won't terminal I/O operations pending on
>> other devices/files.
>
> What do you mean by "abort"? I can accept that "aborting" may
> lead to raising of IOError (which we can catch and retry), but
> not to arbitrary insertion of EOFs into streams (which we
> cannot distinguish from the real deal coming from the user).

It didn't insert an EOF, it just caused read() to return
"prematurely".  You should call read() again until it receives
a _real_ EOF and returns ''.  Take another look at my example:

#!/usr/bin/python
import signal, os, sys

_bTerminalSizeChanged = False

def report_terminal_size_change(signum, frame):
    global _bTerminalSizeChanged
    _bTerminalSizeChanged = True

signal.signal(signal.SIGWINCH, report_terminal_size_change)

while True:
    try:
        s = sys.stdin.read()
        if not s:
            sys.stderr.write("EOF\n")
            break
        sys.stdout.write(s)
    except IOError:
        sys.stderr.write("IOError\n")
    if _bTerminalSizeChanged:
        sys.stderr.write("SIGWINCH\n")
        _bTerminalSizeChanged = False



-- 
Grant Edwards                   grante             Yow!  Yow! It's a hole
                                  at               all the way to downtown
                               visi.com            Burbank!



More information about the Python-list mailing list