如何在Python中获取Linux控制台窗口宽度

326

在Python中有没有一种方法可以自动确定控制台的宽度?我的意思是指不换行情况下适合一行的字符数,而不是窗口的像素宽度。

编辑

寻找适用于Linux的解决方案。


请查看此答案以获得更全面的解决方案,以实现“列依赖”打印机制。 https://stackoverflow.com/questions/44129613/how-to-print-number-of-characters-based-on-terminal-width-that-also-resize/44133299#44133299 - Riccardo Petraglia
2
请考虑更改已接受的答案。您选择的那个答案非常不稳定,依赖于平台,并且正在使用已弃用的 os.popen。得票最高的答案展示了使用 shutil 的最佳方法。 - wim
15个回答

337

不确定为什么它在模块shutil中,但它在Python 3.3中落地。请参见:

查询输出终端的大小

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

os模块中有一个低级实现。跨平台——在Linux、Mac OS和Windows下工作,可能还适用于其他类Unix系统。也有一个后移版本,但已不再相关。


36
因为你不应再使用2.7版本,转向3.x版本是值得的。 - anthonyryan1
6
许多托管提供商尚未支持Python3,因此我们中的一些人被迫使用Python2进行后端开发。尽管这与获取终端大小无关... - whitebeard
4
因为有很多 Python 2 的代码需要迁移工作量太大。此外,Pypy 对 Python 3 的支持仍然比较差。 - Antimony
2
@whitebeard 有没有什么原因导致订阅者不能在VPS上安装Python 3?或者在2016年,人们是否仍在使用不愿意安装Python 3的共享主机?例如,WebFaction共享主机已经安装了python3.5 - Damian Yerrick
3
如果标准输入已从文件重定向,则返回解决方案的+1!使用其他解决方案,我要么收到“Inappropriate ioctl for device”错误/警告,要么得到定义的回退值80。 - pawamoy
显示剩余8条评论

281
import os
rows, columns = os.popen('stty size', 'r').read().split()

使用“stty size”命令,根据Python邮件列表上的一个线程,它在Linux上是相当普遍的。它将“stty size”命令打开为文件,“读取”其中的内容,并使用简单的字符串分割来分离坐标。

与os.environ [“COLUMNS”]值不同(尽管我使用bash作为我的标准shell也无法访问该值),数据也将是最新的,而我认为os.environ [“COLUMNS”]值仅在启动Python解释器时有效(假设用户此后调整了窗口大小)。

(查看@GringoSuave的答案,了解如何在Python 3.3+上执行此操作)


5
默认情况下,Bash不导出COLUMNS变量,这就是为什么os.environ["COLUMNS"]无法工作的原因。 - Kjell Andreassen
42
rows, columns = subprocess.check_output(['stty', 'size']).split() 这个代码稍微短一些,而且 subprocess 是未来。 - cdosborn
10
tput 比 stty 更好,因为 stty 无法与管道一起使用。 - liuyang1
6
rows, columns = subprocess.check_output(['stty', 'size']).decode().split() 如果你需要适用于Python 2/3兼容的Unicode字符串。 - Bryce Guinta
3
除非万不得已,否则不要使用进程(特别是过时的os.popen)来完成此操作。 - Gringo Suave
显示剩余9条评论

66

使用

import console
(width, height) = console.getTerminalSize()

print "Your terminal's width is: %d" % width

编辑:哦,我很抱歉。那不是Python的标准库之一,这是console.py的源代码(我不知道它来自哪里)。

该模块似乎是这样工作的:它检查是否可用 termcap,如果可用,则使用它;如果不行,它将检查终端是否支持特定的 ioctl 调用,如果仍然不行,它会检查某些 shell 导出的环境变量。 这可能仅适用于UNIX。

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

5
感谢您的快速回复,但是在这里(http://effbot.org/zone/console-handbook.htm)上说:“控制台模块目前仅适用于Windows 95、98、NT和2000。”我正在寻找适用于Linux的解决方案。也许从标签上不太清楚,我会相应地编辑问题。 - Sergey Golovchenko
2
由于您正在使用的“console”模块不在标准Python库中,因此您应该提供其源代码或至少提供一个链接。 - nosklo
4
不想让代码变得更复杂,但“cr”是一个令人困惑的名称,因为它暗示元组是(列,行)。实际上,情况正好相反。 - Paul Du Bois
我想知道这段代码是否来自于 https://github.com/nvie/rq/commit/aebfe74630da0e042038d958bdc1e870fbc28f76谢谢你的回答。 - dylanninin
@dylanninin 不是,但可能是反过来的情况 :). 这个回答是2009年的,而你链接的代码是2011年的。这段代码来自我前公司的开源代码库。 - Johannes Weiss
显示剩余12条评论

62

由于winsize结构体中有4个无符号short,而不是2个有符号short,所以上面的代码在我的Linux上没有返回正确的结果:

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和hp应该包含像素宽度和高度,但实际上并没有。


4
应该这样做:请注意,如果您打算在终端上打印内容,应该将'1'作为文件描述符(ioctl的第一个参数)使用,因为stdin可能是管道或不同的tty。 - mic_e
1
也许应该用 fcntl.ioctl(sys.stdin.fileno(), ... 替换掉0。 - raylu
4
这是最佳答案 - 您的用户会很高兴,因为没有出现意外子进程来获取术语宽度。 - zzzeek
4
这确实是最清晰的答案。但我认为你应该使用stdoutstderr而不是stdin。尽管stdin可以是一个管道。您可能还想添加一行代码,如if not os.isatty(0): return float("inf") - mic_e
这在 Chromebook 终端上一些奇怪地运作,因为它几乎没有功能。+1 - hyper-neutrino
显示剩余2条评论

51

可能是以下情况之一:

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

shutil函数只是os函数的一个包装器,用于捕获一些错误并设置回退,但它有一个巨大的缺陷——在管道传输时会出现问题!这是一个相当大的问题。

要在管道传输时获取终端大小,请使用os.get_terminal_size(0)代替。

第一个参数0表示应使用stdin文件描述符,而不是默认的stdout。我们想使用stdin,因为stdout在被管道传输时会分离自身,这种情况下会引发错误。

我试图弄清楚何时使用stdout而不是stdin参数是有意义的,但不知道为什么这里将其设置为默认值。


4
os.get_terminal_size() 函数是在 Python 3.3 中引入的。 - villapx
我首先尝试了shutil,第一次就成功了。我正在使用Python 3.6+。 - Dan
2
如果使用 os.get_terminal_size(0) 并将其管道化到 stdin,程序将会崩溃。 可以尝试以下命令: echo x | python3 -c 'import os; print(os.get_terminal_size(0))' - ehabkost
2
这个答案是四年前给出的。;-) - Gringo Suave

39

我搜索了一下,在这里找到了Windows的解决方案:

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

还有一个适用于Linux的解决方案。

下面是适用于Linux、OS X和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: https://dev59.com/2nVC5IYBdhLWcg3wihmv
    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

你为我节省了自己动手的时间。适用于Linux。应该也能在Windows上运行。谢谢! - Steve V.

24

17
shutil.get_terminal_size() 是常规使用的高级函数,os.get_terminal_size() 是底层实现。 - mid_kid
2
这几乎是一个与上面给出的一年前的答案相同的副本。 - Gringo Suave
4
为什么不能使用os.get_terminal_size()?是否有详细的原因说明? - user3999721
@KarthikKumar 这里的文档链接中已经说明了。 - Sir Code-A-Lot

6

如果在调用该脚本时没有控制终端,这里的许多 Python 2 实现都会失败。您可以检查 sys.stdout.isatty() 确定是否实际上是终端,但这将排除一堆情况,因此我认为找出终端大小的最 pythonic 的方法是使用内置的 curses 包。

import curses
w = curses.initscr()
height, width = w.getmaxyx()

6

看起来,Johannes 的代码存在一些问题:

  • getTerminalSize 需要 import os
  • env 是什么?看起来像是 os.environ.

此外,为什么在返回之前要交换 linescols?如果 TIOCGWINSZstty 都说 lines 然后是 cols,那就保持这种方式。在我注意到不一致之前,这让我困惑了好几分钟。

Sridhar,在我将输出导入时,我没有遇到那个错误。我很确定它在 try-except 中被正确捕获了。

pascal,"HHHH" 在我的机器上不起作用,但 "hh" 可以。我很难找到有关该函数的文档。看起来这是与平台相关的。

chochem,已合并。

这是我的版本:

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)

我也对env感到困惑,确实是env = os.environ,来自被接受的答案 - sdaau

2

尝试使用"blessings"

我也在寻找同样的东西。它非常易于使用,并提供用于在终端中着色、样式化和定位的工具。你所需要的就像这样简单:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

在Linux中表现良好。(我不确定MacOSX和Windows)

下载和文档请点击这里

或者你可以使用pip安装:

pip install blessings

1
现在我会建议尝试使用“blessed”,它是“blessings”的一个当前维护的分支(和增强版)。 - zezollo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接