使用子进程获取实时输出

182 投票
24 回答
190909 浏览
提问于 2025-04-15 11:18

我正在尝试为一个命令行程序(svnadmin verify)写一个包装脚本,目的是在操作时显示一个漂亮的进度指示器。为了做到这一点,我需要能够立即看到被包装程序输出的每一行内容。

我想我可以使用 subprocess.Popen 来执行这个程序,设置 stdout=PIPE,然后读取每一行输出并相应地处理它。但是,当我运行以下代码时,输出似乎被缓冲了,导致它分成了两部分显示:第一部分是第1行到第332行,第二部分是第333行到第439行(最后一行输出)。

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, shell = True)
for line in p.stdout:
    print line.replace('\n', '')

在查看了一下关于subprocess的文档后,我发现了 bufsize 参数,所以我尝试将bufsize设置为1(逐行缓冲)和0(不缓冲),但这两个值似乎都没有改变输出的方式。

这时我开始有点无计可施,于是写了以下的输出循环:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

但结果还是一样。

使用subprocess执行的程序,能否获得“实时”的输出?在Python中有没有其他向前兼容的选项(不是 exec*)?

24 个回答

31

你可以直接把子进程的输出导向不同的流。下面是一个简单的例子:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)
42

通过将缓冲区大小设置为1,你实际上是在强制程序不对输出进行缓冲。

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()
94

我试过这个,出于某种原因,虽然这段代码

for line in p.stdout:
  ...

在缓存方面表现得很积极,但这个变体

while True:
  line = p.stdout.readline()
  if not line: break
  ...

却没有。显然这是一个已知的bug:http://bugs.python.org/issue3907(这个问题在2018年8月29日已经“关闭”了)

撰写回答