在 Python 中为当前进程设置终端宽度

Set terminal width in Python for the current process

我想在 Python 中使用 \r 做一个控制台进度条来清除显示。为了支持多行进度条(用于多个并行任务),我想知道是否可以为当前进程设置终端宽度,以便长行可以换行?

例如,如果我将终端宽度设置为 20,我可以这样设置进度条:

print '\r%-20s%-20s' % ('Task 1: 30%', 'Task 2: 60%'),
time.sleep(1)
print '\r%-20s%-20s' % ('Task 1: 35%', 'Task 2: 65%'),

我知道可以使用 ncurses(或使用 ncurses 的进度条包),我只是想知道是否有不使用 ncurses 的解决方案。

我已经从 尝试了 set_winsize,但它没有用,线仍然没有换行。

谢谢!

使用指定的方法更改终端宽度(rxvt 从 xterm 中获得的功能 based on a feature from dtterm)may/may 不起作用,具体取决于您使用的实际终端。但是还有更直接的方法。

与其写一长串希望终端将其换行,不如

  • 写一个转义序列将光标移动到左上角,
  • 写下你的进度条(用转义序列清除每一行的剩余部分),并明确地包装它们,并且
  • 写一个转义序列以清除之前的屏幕剩余部分
  • 稍等片刻再重复该过程

这是一个例子:

import curses, time, sys

def go_to_top_left():
    curses.putp(curses.tparm(curses.tigetstr("cup"),0,0))

def show_bar(n):
    curses.putp(curses.tigetstr("rev"))
    sys.stdout.write('.' * n)
    sys.stdout.write('*')
    curses.putp(curses.tigetstr("sgr0"))
    curses.putp(curses.tigetstr("el"))
    sys.stdout.write('\n')
    sys.stdout.flush()

def clear_to_bottom():
    curses.putp(curses.tigetstr("ed"))

def progress_bars():
    curses.setupterm()
    foo = 1
    bar = 0
    while foo < 50:
        go_to_top_left()
        show_bar(foo)
        show_bar(bar)
        bar = foo
        foo = foo + 1
        clear_to_bottom()
        time.sleep(1)

progress_bars()

该示例使用来自 curses 包的 terminfo 调用。与 termcap 一样,这些调用不会 优化 光标的移动,并且您可以控制实际清除屏幕的时间。如果你想完全放弃清算,你必须做出几个关于

的假设
  • 进度条的宽度,
  • 屏幕上已有内容,并且
  • 您在屏幕上的哪个位置执行此操作。

(有人可能会提供与此示例等效的硬编码 "improvement")。

延伸阅读:

与其尝试设置终端宽度,不如适应终端大小的变化。

这个小程序就是这样做的。 运行 它并调整终端的大小;观察输出调整大小。

我从 this elaborate example 中获取了 IOCTL 代码。

import fcntl
import signal
import struct
import sys
import termios
import time

def ioctl_GWINSZ(fd): #### TABULATION FUNCTIONS
    return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))


def draw_line(width, left_cap, body, right_cap):
    w = sys.stdout.write
    w(left_cap)
    w(body * (width - 2))
    w(right_cap)


def draw_rect(width, height):
    draw_line(width, '/', '-', '\')
    for n in xrange(height - 3):
        draw_line(width, '|', ' ', '|')
    draw_line(width, '\', '-', '/')


def draw_screen():
    h, w = ioctl_GWINSZ(1)  # of stdout.
    draw_rect(w, h)
    sys.stdout.write('Rows: %3d, Cols: %3d' % (h, w))
    sys.stdout.flush()


def on_winch(*ignored):
    sys.stdout.write('\n')
    draw_screen()

if __name__ == '__main__':
    draw_screen()
    signal.signal(signal.SIGWINCH, on_winch)
    while True:
        time.sleep(0.1)