从subprocess.communicate()读取流输入

时间:2010-04-26 18:23:18

标签: python subprocess

我正在使用Python的subprocess.communicate()从运行大约一分钟的进程中读取stdout。

如何以流式方式打印出该进程的stdout的每一行,以便我可以看到生成的输出,但在继续之前仍然阻止进程终止?

subprocess.communicate()似乎可以立即提供所有输出。

7 个答案:

答案 0 :(得分:130)

一旦子进程刷新其stdout缓冲区,就要逐行获取子进程的输出:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter()用于在写入解决方法the read-ahead bug in Python 2后立即读取行。

如果子进程'stdout在非交互模式下使用块缓冲而不是行缓冲(导致输出延迟,直到子缓冲区已满或由孩子明确刷新),那么你可以尝试强制使用pexpect, pty modulesunbuffer, stdbuf, script utilities的无缓冲输出,请参阅Q: Why not just use a pipe (popen())?


这是Python 3代码:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

注意:与Python 2不同,它按原样输出子进程'bytestrings; Python 3使用文本模式(cmd的输出使用locale.getpreferredencoding(False)编码进行解码)。

答案 1 :(得分:41)

请注意,我认为J.F. Sebastian's method (below)更好。


这是一个简单的例子(没有检查错误):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

如果ls结束得太快,那么while循环可能会在您读取所有数据之前结束。

你可以用这种方式捕获stdout中的剩余部分:

output = proc.communicate()[0]
print output,

答案 2 :(得分:5)

我认为以流媒体方式从流程中收集输出的最简单方法是这样的:

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

readline()read()函数应该只在进程终止后在EOF上返回一个空字符串 - 否则如果没有要读取的内容它会阻塞(readline()包含换行符,所以在空行上,它返回“\ n”)。这避免了在循环之后需要进行尴尬的最终communicate()调用。

对于具有很长行read()的文件,最好减少最大内存使用量 - 传递给它的数字是任意的,但排除它会导致一次读取整个管道输出,这可能是不可取的。< / p>

答案 3 :(得分:3)

如果您想要非阻止方法,请不要使用process.communicate()。如果您将subprocess.Popen()参数stdout设置为PIPE,则可以从process.stdout进行阅读,并使用process.poll()检查流程是否仍在运行。

答案 4 :(得分:2)

如果您只是想通过实时传递输出,那么很难比这简单:

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

请参阅docs for subprocess.check_call()

如果您需要处理输出,请确保循环。但如果你不这样做,那就保持简单。

编辑: J.F. Sebastian指出stdout和stderr参数的默认值都传递给sys.stdout和sys.stderr,如果是sys.stdout,则会失败sys.stderr已被替换(例如,用于捕获测试中的输出)。

答案 5 :(得分:1)

myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))

答案 6 :(得分:0)

添加具有一些小的更改的另一个python3解决方案:

  1. 允许您捕获shell进程的退出代码(使用with构造时,我无法获得退出代码)
  2. 还实时输出stderr
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
    # Run a shell command, streaming output to STDOUT in real time
    # Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
    for line in p.stdout:
        sys.stdout.write(line)
    p.wait()
    exit_code = p.returncode
    if exit_code != 0 and fail_on_error:
        raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
    return(exit_code)
相关问题