我正在尝试为命令行程序(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', '')

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

此时我开始抓住救命稻草,所以我编写了以下输出循环:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

但得到了相同的结果。

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

有帮助吗?

解决方案

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

for line in p.stdout:
  ...

积极缓冲,变体

while True:
  line = p.stdout.readline()
  if not line: break
  ...

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

其他提示

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

你可以试试这个:

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()

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

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

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)

不久前我遇到了同样的问题。我的解决方案是放弃迭代 read 方法,即使您的子进程尚未完成执行,该方法也会立即返回,等等。

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

<< C程序>>

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

<<Python程序>>

#!/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)

<<输出>>打印:计数1打印:计数2打印:数3

希望能帮助到你。

〜赛拉姆

您可以对子进程输出中的每个字节使用迭代器。这允许从子进程进行内联更新(以 ' ' 结尾的行覆盖先前的输出行):

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)


# read each byte of subprocess
while subprocess.poll() is None:
    for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):
        c = c.decode('ascii')
        sys.stdout.write(c)
sys.stdout.flush()

if subprocess.returncode != 0:
    raise Exception("The subprocess did not terminate correctly.")

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

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

os.environ["PYTHONUNBUFFERED"] = "1"

或者将其传递到 env 论证 Popen.

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

cmd = ["stdbuf", "-oL"] + cmd

也可以看看 这里 关于 stdbuf 或其他选项。

(也可以看看 这里 以获得相同的答案。)

在 Python 中使用 asyncio 流式传输子进程 stdin 和 stdout 博客文章由 凯文·麦卡锡 展示了如何使用 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())

使用 pexpect [ http://www.noah.org/wiki/Pexpect ] 使用非阻塞读取行将解决这个问题。它源于管道被缓冲的事实,因此您的应用程序的输出被管道缓冲,因此在缓冲区填满或进程终止之前您无法获得该输出。

我使用这个解决方案来获取子进程的实时输出。一旦进程完成,该循环就会停止,无需使用break语句或可能的无限循环。

sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:
    out = sub_process.stdout.read(1)
    sys.stdout.write(out)
    sys.stdout.flush()

发现了这个“即插即用”功能 这里. 。效果非常好!

import subprocess

def myrun(cmd):
    """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
    """
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        print line,
        if line == '' and p.poll() != None:
            break
    return ''.join(stdout)

完整的解决方案:

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()

这是我经常使用的基本框架。它可以轻松实现超时,并且能够处理不可避免的挂起过程。

import subprocess
import threading
import Queue

def t_read_stdout(process, queue):
    """Read from stdout"""

    for output in iter(process.stdout.readline, b''):
        queue.put(output)

    return

process = subprocess.Popen(['dir'],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           bufsize=1,
                           cwd='C:\\',
                           shell=True)

queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()

while process.poll() is None or not queue.empty():
    try:
        output = queue.get(timeout=.5)

    except Queue.Empty:
        continue

    if not output:
        continue

    print(output),

t_stdout.join()

(此解决方案已使用Python 2.7.15进行测试)
您只需要在每行读/写之后 sys.stdout.flush() :

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top