类似于apt的列输出 - python库

3 投票
4 回答
3226 浏览
提问于 2025-04-15 14:10

Debian的apt工具输出的结果是以统一宽度的列显示的。比如,你可以试着运行“aptitude search svn”,你会发现所有的名字都在第一列,宽度是一样的。

如果你调整终端的大小,列的宽度也会相应地调整。

有没有一个Python库可以做到这一点?需要注意的是,这个库必须能够识别终端的宽度,并且能接受一个表格作为输入,比如说 [('rapidsvn', 'A GUI client for subversion'), ...]。你还可以为第一列(或任何一列)指定一个最大宽度。另外,如果第二列的字符串超过了终端的宽度,它会被截断,这样就不会出现不想要的换行。

$ aptitude search svn
[...]
p   python-svn-dbg                    - A(nother) Python interface to Subversion (d
v   python2.5-svn                     -                                            
v   python2.6-svn                     -                                            
p   rapidsvn                          - A GUI client for subversion                
p   statsvn                           - SVN repository statistics                  
p   svn-arch-mirror                   - one-way mirroring from Subversion to Arch r
p   svn-autoreleasedeb                - Automatically release/upload debian package
p   svn-buildpackage                  - helper programs to maintain Debian packages
p   svn-load                          - An enhanced import facility for Subversion 
p   svn-workbench                     - A Workbench for Subversion                 
p   svnmailer                         - extensible Subversion commit notification t
p   websvn                            - interface for subversion repositories writt
$

编辑: (针对下面Alex的回答)... 输出的格式会类似于'aptiude search',具体来说:1)只有最后一列(这是行中字符串最长的那一列)会被截断,2)通常只有2到4列,但最后一列(“描述”)预计会占据至少一半的终端宽度。3)所有行的列数是相等的,4)所有条目都是字符串。

4 个回答

2

好吧,aptitude这个工具使用了cwidget来格式化文本显示中的列。你可以通过写一个Python扩展来调用cwidget,不过我觉得这样做不太值得……你可以用自己喜欢的方法来获取实际的字符宽度,然后自己计算就可以了。

4

更新: 现在可以在applib这个Python库中找到colprint这个功能,具体代码可以在GitHub上查看

对于感兴趣的朋友,这里有完整的程序:

# This function was written by Alex Martelli
# http://stackoverflow.com/questions/1396820/
def colprint(table, totwidth=None):
    """Print the table in terminal taking care of wrapping/alignment

    - `table`:    A table of strings. Elements must not be `None`
    - `totwidth`: If None, console width is used
    """
    if not table: return
    if totwidth is None:
        totwidth = find_console_width()
        totwidth -= 1 # for not printing an extra empty line on windows
    numcols = max(len(row) for row in table)
    # ensure all rows have >= numcols columns, maybe empty
    padded = [row+numcols*('',) for row in table]
    # compute col widths, including separating space (except for last one)
    widths = [ 1 + max(len(x) for x in column) for column in zip(*padded)]
    widths[-1] -= 1
    # drop or truncate columns from the right in order to fit
    while sum(widths) > totwidth:
        mustlose = sum(widths) - totwidth
        if widths[-1] <= mustlose:
            del widths[-1]
        else:
            widths[-1] -= mustlose
            break
    # and finally, the output phase!
    for row in padded:
        print(''.join([u'%*s' % (-w, i[:w])
                       for w, i in zip(widths, row)]))

def find_console_width():
    if sys.platform.startswith('win'):
        return _find_windows_console_width()
    else:
        return _find_unix_console_width()
def _find_unix_console_width():
    """Return the width of the Unix terminal

    If `stdout` is not a real terminal, return the default value (80)
    """
    import termios, fcntl, struct, sys

    # fcntl.ioctl will fail if stdout is not a tty
    if not sys.stdout.isatty():
        return 80

    s = struct.pack("HHHH", 0, 0, 0, 0)
    fd_stdout = sys.stdout.fileno()
    size = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s)
    height, width = struct.unpack("HHHH", size)[:2]
    return width
def _find_windows_console_width():
    """Return the width of the Windows console

    If the width cannot be determined, return the default value (80)
    """
    # http://code.activestate.com/recipes/440694/
    from ctypes import windll, create_string_buffer
    STDIN, STDOUT, STDERR = -10, -11, -12

    h = windll.kernel32.GetStdHandle(STDERR)
    csbi = create_string_buffer(22)
    res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)

    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
    else:
        sizex, sizey = 80, 25

    return sizex
2

我觉得没有一种通用的方法可以“获取终端的宽度”——绝对不是“查看 COLUMNS 环境变量”(可以看看我对这个问题的评论)。在 Linux 和 Mac OS X(我想所有现代的 Unix 版本也是这样),

curses.wrapper(lambda _: curses.tigetnum('cols'))

可以返回列的数量;但我不知道 wcurses 在 Windows 上是否支持这个功能。

一旦你得到了想要的输出宽度(可以通过 os.environ['COLUMNS'],或者使用 curses,或者从其他地方获取,或者默认设置为 80,或者你喜欢的任何方式),接下来的工作就比较简单了。不过这项工作比较麻烦,容易出现一些小错误,而且很容易受到一些细节的影响,这些细节你没有完全说明,比如:为了避免换行,哪一列会被截断——总是最后一列吗,还是其他的?你在示例输出中显示了 3 列,但根据你的问题只传入了 2 列,这是怎么回事?如果不是所有行都有相同数量的列,该怎么办?表格中的所有条目必须都是字符串吗?还有很多类似的疑问。

所以,对于你没有表达的所有细节,我们可以做一些相对随意的猜测,一种方法可能是这样的……:

import sys

def colprint(totwidth, table):
  numcols = max(len(row) for row in table)
  # ensure all rows have >= numcols columns, maybe empty
  padded = [row+numcols*('',) for row in table]
  # compute col widths, including separating space (except for last one)
  widths = [ 1 + max(len(x) for x in column) for column in zip(*padded)]
  widths[-1] -= 1
  # drop or truncate columns from the right in order to fit
  while sum(widths) > totwidth:
    mustlose = sum(widths) - totwidth
    if widths[-1] <= mustlose:
      del widths[-1]
    else:
      widths[-1] -= mustlose
      break
  # and finally, the output phase!
  for row in padded:
    for w, i in zip(widths, row):
      sys.stdout.write('%*s' % (-w, i[:w]))
    sys.stdout.write('\n')

撰写回答