通过stdout和PIPE的实时subprocess.Popen

26 投票
8 回答
52005 浏览
提问于 2025-04-15 18:08

我正在尝试从一个 subprocess.Popen 调用中获取 stdout(标准输出),虽然我通过以下方法很容易实现:

cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE)
for line in cmd.stdout.readlines():
    print line

但是我希望能够实时获取 stdout。因为上面的方法是等到所有的 stdout 都抓取完后才返回。

所以对于记录日志来说,这并不能满足我的需求(比如,我想在事情发生的时候就能“看到”发生了什么)。

有没有办法在运行时逐行获取 stdout?还是说这是 subprocess 的一个限制(必须等到 PIPE 关闭后才能获取)?

编辑:如果我把 readlines() 换成 readline(),我只会得到 stdout 的最后一行(这并不好):

In [75]: cmd = Popen('ls -l', shell=True, stdout=PIPE)
In [76]: for i in cmd.stdout.readline(): print i
....: 
t
o
t
a
l

1
0
4

8 个回答

11

如果你想要实时获取输出,subprocess就不太合适,因为它无法绕过其他进程的缓冲策略。这就是我总是推荐在需要这种“实时”输出时使用pexpect的原因(除了Windows系统——在Windows上,可以使用wexpect)。

19

其实,真正的解决办法是直接把子进程的输出(stdout)重定向到你当前进程的输出。

实际上,按照你现在的方法,你只能同时打印标准输出(stdout),而不能打印错误输出(stderr)。

import sys
from subprocess import Popen
Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate()

使用communicate()这个方法是为了让调用在子进程结束之前一直阻塞,也就是说,它会等到子进程完成后再继续执行下一行代码。否则,程序可能会在子进程还没结束的时候就提前结束了(不过,即使你的Python脚本关闭了,重定向到你的标准输出仍然有效,我测试过)。

这样一来,你就可以同时重定向标准输出和错误输出,而且是实时的。

比如,在我的测试中,我用这个脚本slow_cmd_output.sh进行了测试:

#!/bin/bash

for i in 1 2 3 4 5 6; do sleep 5 && echo "${i}th output" && echo "err output num ${i}" >&2; done
23

你的解释器在缓存数据。可以在打印语句后面加上一个调用 sys.stdout.flush() 的命令。

撰写回答