使用子进程获取实时输出

Chr*_*ieb 124 python subprocess

我正在尝试为命令行程序(svnadmin verify)编写一个包装器脚本,它将为操作显示一个很好的进度指示器.这要求我能够在输出后立即查看包装程序的每一行输出.

我想我只是执行程序使用subprocess.Popen,使用stdout=PIPE,然后读取每一行,并相应地对其进行操作.但是,当我运行以下代码时,输​​出似乎在某处缓冲,导致它出现在两个块中,第1行到第332行,然后是333到439(输出的最后一行)

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, shell = True)
for line in p.stdout:
    print line.replace('\n', '')
Run Code Online (Sandbox Code Playgroud)

稍微查看子进程的文档后,我发现bufsize参数为Popen,所以我尝试将bufsize设置为1(缓冲每行)和0(无缓冲区),但这两个值似乎都没有改变行的传递方式.

此时我开始掌握吸管,所以我编写了以下输出循环:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break
Run Code Online (Sandbox Code Playgroud)

但得到了相同的结果.

是否有可能获得使用子进程执行的程序的"实时"程序输出?Python中是否有其他选项可以向前兼容(不是exec*)?

Dav*_*ave 78

我尝试了这个,并且由于某种原因而在代码中

for line in p.stdout:
  ...
Run Code Online (Sandbox Code Playgroud)

缓慢地缓冲,变种

while True:
  line = p.stdout.readline()
  if not line: break
  ...
Run Code Online (Sandbox Code Playgroud)

才不是.显然这是一个已知的错误:http://bugs.python.org/issue3907(该问题现已截至2018年8月29日"已关闭")

  • 更好的是,使用`for line in iter(p.stdout.readline,""):` (21认同)
  • @exhuma:它工作正常.readline在空行返回"\n",不计算为true.它只在管道关闭时返回一个空字符串,这将在子进程终止时返回. (6认同)
  • 如果子进程返回空行,则此代码将中断.一个更好的解决方案是使用`while p.poll()是None`而不是`while True`,并删除`if not line` (3认同)
  • 同样,为了实时实时读取进程的输出,您将需要告诉python您不需要任何缓冲。亲爱的Python,直接给我输出。方法如下:您需要设置环境变量`PYTHONUNBUFFERED = 1`。这对于无穷大的输出特别有用 (2认同)

Cor*_*erg 37

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()
Run Code Online (Sandbox Code Playgroud)

  • @nbro 可能是因为没有解释就给出了代码......:/ (3认同)
  • 这个b''是什么意思? (3认同)
  • @ManuelSchneid3r `iter(<callable>, <string>)` 使用 <callable> 的每个输出创建一个迭代器,直到它返回 <string> (称为 `sentinel`)。如果您多次尝试运行“p.stdout.readline”,您会发现当它没有其他内容可打印时,它会打印“b”,因此这是在这种情况下使用的适当哨兵。 (2认同)

Nad*_*mli 18

你可以试试这个:

import subprocess
import sys

process = subprocess.Popen(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

while True:
    out = process.stdout.read(1)
    if out == '' and process.poll() != None:
        break
    if out != '':
        sys.stdout.write(out)
        sys.stdout.flush()
Run Code Online (Sandbox Code Playgroud)

如果使用readline而不是read,则会出现一些未打印输入消息的情况.尝试使用命令需要内联输入并亲自查看.

  • 这应该无限期挂起吗?我希望给定的解决方案还包括用于在初始子进程完成时编辑循环的样板代码.对不起,无论我多长时间研究一下,subprocess etcetera都是我无法工作的东西. (3认同)
  • 这是长期工作的最佳解决方案.但它应该使用不是无而不是!=无.你不应该使用!= with None. (2认同)

Aid*_*man 18

您可以直接将子进程输出定向到流.简化示例:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)
Run Code Online (Sandbox Code Playgroud)

  • 这不是“实时”,这是这个问题的重点。这会等到“ls”完成运行,并且不会让您访问其输出。(此外,“stdout”和“stderr”关键字参数是多余的 - 您只需显式指定默认值。) (3认同)
  • 这是否允许您在“.communicate()”中事后获取内容?或者内容是否丢失到父 stderr/stdout 流中? (2认同)

pav*_*mok 7

在 Python 3.x 中,该过程可能会挂起,因为输出是字节数组而不是字符串。确保将其解码为字符串。

从 Python 3.6 开始,您可以使用Popen Constructor 中的参数encoding来完成。完整示例:

process = subprocess.Popen(
    'my_command',
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    shell=True,
    encoding='utf-8',
    errors='replace'
)

while True:
    realtime_output = process.stdout.readline()

    if realtime_output == '' and process.poll() is not None:
        break

    if realtime_output:
        print(realtime_output.strip(), flush=True)
Run Code Online (Sandbox Code Playgroud)

请注意,此代码重定向 stderrstdout处理输出错误


小智 6

实时输出问题已解决:我在 Python 中遇到了类似的问题,同时捕获 C 程序的实时输出。我添加fflush(stdout);了我的 C 代码。它对我有用。这是代码。

C程序:

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}
Run Code Online (Sandbox Code Playgroud)

蟒蛇程序:

#!/usr/bin/python

import os, sys
import subprocess


procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:
    line = procExe.stdout.readline()
    print("Print:" + line)
Run Code Online (Sandbox Code Playgroud)

输出:

Print: Count  1
Print: Count  2
Print: Count  3
Run Code Online (Sandbox Code Playgroud)


Alb*_*ert 5

根据用例,您可能还想禁用子进程本身的缓冲。

如果子进程是一个 Python 进程,您可以在调用之前执行以下操作:

os.environ["PYTHONUNBUFFERED"] = "1"
Run Code Online (Sandbox Code Playgroud)

或者将其在env参数中传递给Popen.

否则,如果您使用的是 Linux/Unix,则可以使用该stdbuf工具。例如:

cmd = ["stdbuf", "-oL"] + cmd
Run Code Online (Sandbox Code Playgroud)

另请参阅此处有关stdbuf或其他选项。

(另请参阅此处以获得相同的答案。)


Pab*_*blo 5

流子stdin和stdout与ASYNCIO在Python的博客文章凯文·麦卡锡显示了如何ASYNCIO做到这一点:

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
Run Code Online (Sandbox Code Playgroud)