【问题标题】:Displaying subprocess output to stdout and redirecting it将子进程输出显示到标准输出并重定向它
【发布时间】:2014-11-03 05:16:35
【问题描述】:

我正在通过 Python 的子进程模块运行脚本。目前我使用:

p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = p.communicate()

然后我将结果打印到标准输出。这一切都很好,但是由于脚本需要很长时间才能完成,所以我也希望从脚本实时输出到标准输出。我管道输出的原因是因为我想解析它。

【问题讨论】:

标签: python subprocess stdout


【解决方案1】:

将子进程的标准输出保存到变量以供进一步处理和display it while the child process is running as it arrives

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

with Popen('/path/to/script', stdout=PIPE, bufsize=1,
           universal_newlines=True) as p, StringIO() as buf:
    for line in p.stdout:
        print(line, end='')
        buf.write(line)
    output = buf.getvalue()
rc = p.returncode

同时保存子进程的标准输出和标准错误更复杂,因为你应该consume both streams concurrently to avoid a deadlock:

stdout_buf, stderr_buf = StringIO(), StringIO()
rc =  teed_call('/path/to/script', stdout=stdout_buf, stderr=stderr_buf,
                universal_newlines=True)
output = stdout_buf.getvalue()
...

teed_call() is define here.


更新:这里是a simpler asyncio version


旧版本:

这里有一个基于child_process.py example from tulip的单线程解决方案:

import asyncio
import sys
from asyncio.subprocess import PIPE

@asyncio.coroutine
def read_and_display(*cmd):
    """Read cmd's stdout, stderr while displaying them as they arrive."""
    # start process
    process = yield from asyncio.create_subprocess_exec(*cmd,
            stdout=PIPE, stderr=PIPE)

    # read child's stdout/stderr concurrently
    stdout, stderr = [], [] # stderr, stdout buffers
    tasks = {
        asyncio.Task(process.stdout.readline()): (
            stdout, process.stdout, sys.stdout.buffer),
        asyncio.Task(process.stderr.readline()): (
            stderr, process.stderr, sys.stderr.buffer)}
    while tasks:
        done, pending = yield from asyncio.wait(tasks,
                return_when=asyncio.FIRST_COMPLETED)
        assert done
        for future in done:
            buf, stream, display = tasks.pop(future)
            line = future.result()
            if line: # not EOF
                buf.append(line)    # save for later
                display.write(line) # display in terminal
                # schedule to read the next line
                tasks[asyncio.Task(stream.readline())] = buf, stream, display

    # wait for the process to exit
    rc = yield from process.wait()
    return rc, b''.join(stdout), b''.join(stderr)

脚本运行'/path/to/script 命令并同时逐行读取其标准输出和标准错误。这些行相应地打印到父级的 stdout/stderr 并保存为字节串以供将来处理。要运行read_and_display() 协程,我们需要一个事件循环:

import os

if os.name == 'nt':
    loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()
try:
    rc, *output = loop.run_until_complete(read_and_display("/path/to/script"))
    if rc:
        sys.exit("child failed with '{}' exit code".format(rc))
finally:
    loop.close()

【讨论】:

    【解决方案2】:

    p.communicate() waits for the subprocess to complete 然后立即返回其全部输出。

    您是否尝试过类似的方法,逐行读取子流程输出?

    p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    for line in p.stdout:
      # do something with this individual line
      print line
    

    【讨论】:

    • 如果子进程生成足够的输出来填充 OS stderr 管道缓冲区(我的机器上为 65K),那么它会挂起。您也应该同时使用p.stderr。由于预读错误,for line in p.stdout 将连续打印。您可以改用for line in iter(p.stdout.readline, b'')print line 将打印双换行符。您可以使用print line,(注意:逗号)来避免它。
    • 关于消费stderr 的好点。我假设在冗长的数据流中,几行缓冲不会成为问题,但这也是需要考虑的问题。
    • "脚本需要很长时间才能完成" -- 这意味着如果脚本将进度写入标准错误,那么它可以停止。
    【解决方案3】:

    Popen.communicate 文档明确指出:

    Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
    

    https://docs.python.org/2/library/subprocess.html#subprocess.Popen.communicate

    所以如果你需要实时输出,你需要使用这样的东西:

    stream_p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    while stream_line in stream_p:
        #Parse it the way you want
        print stream_line
    

    【讨论】:

      【解决方案4】:

      这会将 stdout 和 stderr 打印到终端,并将 stdout 和 stderr 保存到变量中:

      from subprocess import Popen, PIPE, STDOUT
      
      with Popen(args, stdout=PIPE, stderr=STDOUT, text=True, bufsize=1) as p:
          output = "".join([print(buf, end="") or buf for buf in p.stdout])
      

      但是,根据您的具体操作,这可能很重要:通过使用 stderr=STDOUT,我们无法再区分 stdout 和 stderr 并且通过调用 print,您的输出将始终被打印到stdout,不管是来自stdout还是stderr。

      对于 Python universal_newlines 而不是 text

      3.7 版中的新功能:添加文本作为 Universal_newlines 的更易读别名。

      来源:https://docs.python.org/3/library/subprocess.html#subprocess.Popen

      【讨论】:

        猜你喜欢
        • 2022-12-14
        • 2016-02-29
        • 2013-06-02
        • 1970-01-01
        • 2013-07-16
        • 1970-01-01
        • 1970-01-01
        • 2012-08-15
        • 1970-01-01
        相关资源
        最近更新 更多