逐行读取子进程的标准输出

314 投票
14 回答
550913 浏览
提问于 2025-04-15 22:33

我的Python脚本使用subprocess来调用一个在Linux上运行的工具,这个工具输出的信息非常多。我想把所有的输出存到一个日志文件里,同时也想把一部分输出显示给用户。我原以为下面的代码可以实现这个目的,但实际上,输出在我的应用程序中显示出来要等到这个工具产生了大量的输出后才会出现。

# fake_utility.py, just generates lots of output over time
import time
i = 0
    while True:
        print(hex(i)*512)
        i += 1
        time.sleep(0.5)

在父进程中:

import subprocess

proc = subprocess.Popen(['python', 'fake_utility.py'], stdout=subprocess.PIPE)
for line in proc.stdout:
    # the real code does filtering here
    print("test:", line.rstrip())

我真正想要的效果是,过滤脚本能够在从子进程接收到每一行输出时就立即打印出来,像tee命令那样,但我希望在Python代码中实现。

我漏掉了什么呢?这真的可能吗?


14 个回答

29

确实,如果你解决了迭代器的问题,那么现在可能是缓冲的问题。你可以告诉子进程中的Python不要对它的输出进行缓冲。

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

变成

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

我在从Python内部调用Python时需要这个。

101

虽然来得有点晚,但我很惊讶这里没有看到我认为最简单的解决方案:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(这需要使用Python 3。)

241

我觉得问题出在这句代码 for line in proc.stdout,它会在开始循环之前先把所有输入都读完。解决这个问题的方法是用 readline() 来替代:

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

当然,你还得处理一下子进程的缓冲问题。

注意:根据文档,使用迭代器的解决方案应该和用 readline() 是等效的,除了读取前的缓冲区,但(或者正因为这个原因)我发现提议的修改在我这里(Windows XP上的Python 2.5)产生了不同的结果。

撰写回答