使用subprocess.PIPE实现增量输出

2 投票
2 回答
2580 浏览
提问于 2025-04-16 19:03

我正在使用 subprocess 模块来从另一个应用程序运行命令。我知道你可以这样做:

import subprocess

app = subprocess(args, stdout=subprocess.PIPE)
out, err = app.communicate()
print out

我希望输出能够实时显示,而不是最后一次性显示一大堆内容。有什么想法吗?

2 个回答

2

我之前做过一个Python的命令行实现,所以这里有我用来运行命令的代码。它可以正常运行 wgetnano,所以我觉得这段代码应该能满足你的需求:

  process = subprocess.Popen(shlex.split(command), stdout = subprocess.PIPE, stderr = subprocess.STDOUT)

  while True:
    output = process.stdout.read(1)

    if output == '' and process.poll() != None:
      break

    if output != '':
      sys.stdout.write(output)
      sys.stdout.flush()

基本上,你需要直接写入 stdout

7

问题可能出在你在子进程中运行的命令会把输出内容缓存起来:在这种情况下,Blender的回答中提到的 process.stdout.read(1) 可能要等到子进程的输出缓存满了,才会返回结果,这样才会被父进程看到。

想了解缓存如何影响这些内容,可以看看 这个回答这个回答

这里有一些脚本来说明这个问题:

# spew.py
import sys
import time

for i in range(10):
    print 'Message no. %d' % i
    #sys.stdout.flush()
    time.sleep(1)

还有

# runspew.py
import subprocess
import time

start = time.time()
p = subprocess.Popen(['python', 'spew.py'], stdout=subprocess.PIPE)
data = p.stdout.read(1)
print 'Elapsed time to first byte read: %.2f' % (time.time() - start)
data = p.stdout.read()
print 'Elapsed time to last byte read: %.2f' % (time.time() - start)
p.wait()

现在,把这两个脚本放在同一个文件夹里,然后运行 python runspew.py。你会看到类似这样的输出:

Elapsed time to first byte read: 10.05
Elapsed time to last byte read: 10.05

接下来,去掉 spew.pysys.stdout.flush() 的注释,然后再运行 python runspew.py。这时你会看到类似这样的输出:

Elapsed time to first byte read: 0.02
Elapsed time to last byte read: 10.04

注意到 runspew.py 没有变化:只有作为子进程运行的程序发生了变化。如果你运行的程序不能设置为无缓存(或者不频繁刷新),那么你就需要使用 expectunbuffer,就像我之前提到的其他回答中所描述的那样。

撰写回答