Python 使用子流程获取实时输出

Python 使用子流程获取实时输出,python,subprocess,Python,Subprocess,我正在尝试为命令行程序(svnadmin verify)编写一个包装器脚本,它将为操作显示一个良好的进度指示器。这要求我能够在包装好的程序输出后立即看到它的每一行输出 我想我只需要使用subprocess.Popen执行程序,使用stdout=PIPE,然后读取输入的每一行并相应地执行它。然而,当我运行下面的代码时,输出似乎在某个地方被缓冲,导致它出现在两个块中,从第1行到第332行,然后是第333行到第439行(输出的最后一行) 在查看了一些关于子流程的文档后,我发现bufsize参数为Pop

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

我想我只需要使用
subprocess.Popen
执行程序,使用
stdout=PIPE
,然后读取输入的每一行并相应地执行它。然而,当我运行下面的代码时,输出似乎在某个地方被缓冲,导致它出现在两个块中,从第1行到第332行,然后是第333行到第439行(输出的最后一行)

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

在这一点上,我开始抓住救命稻草,因此我编写了以下输出循环:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break
但是得到了同样的结果

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

您可以尝试以下方法:

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,则在某些情况下输入消息不会打印。用一个需要内联输入的命令试试看。

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

我尝试了这个方法,出于某种原因,当代码

for line in p.stdout:
  ...
这个变种

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

没有。显然,这是一个已知的错误:(该问题现已于2018年8月29日“关闭”)

与非阻塞读线一起使用将解决此问题。它源于管道被缓冲的事实,因此应用程序的输出被管道缓冲,因此在缓冲区填满或进程死亡之前,您无法获得该输出。

通过将缓冲区大小设置为1,您基本上强制进程不缓冲输出

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

我使用此解决方案在子流程上获得实时输出。该循环将在进程完成后立即停止,不需要中断语句或可能的无限循环

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

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

C程序:

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

Print: Count  1
Print: Count  2
Print: Count  3
找到此“即插即用”功能。工作得很有魅力

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)

可以对子流程输出中的每个字节使用迭代器。这允许对子流程进行内联更新(以“\r”结尾的行覆盖上一个输出行):

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.")

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

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

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

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

根据用例的不同,您可能还希望禁用子流程本身中的缓冲

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

os.environ["PYTHONUNBUFFERED"] = "1"
或者在
env
参数中将其传递给
Popen

否则,如果您在Linux/Unix上,则可以使用
stdbuf
工具。例如:

cmd = ["stdbuf", "-oL"] + cmd
另请参见关于
stdbuf
或其他选项

(同样的答案也请参见。)

作者的博客文章展示了如何使用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())
(此解决方案已使用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()

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

从Python3.6开始,您可以使用中的参数
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)

请注意,下面的代码
stderr
stdout

 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,)
    stdout = []
    while True:
        line = p.stdout.readline()
        if not isinstance(line, (str)):
            line = line.decode('utf-8')
        stdout.append(line)
        print (line)
        if (line == '' and p.poll() != None):
            break

如果您只想将日志实时转发到控制台

下面的代码将适用于这两种情况

 p = subprocess.Popen(cmd,
                         shell=True,
                         cwd=work_dir,
                         bufsize=1,
                         stdin=subprocess.PIPE,
                         stderr=sys.stderr,
                         stdout=sys.stdout)

您是否尝试过省略
sydout=PIPE
以便子进程直接写入控制台,绕过父进程?问题是我想读取输出。如果它是直接输出到控制台的,我该怎么做呢?另外,我不想让用户看到包装程序的输出,只是我的输出。那为什么要显示“实时”呢?我不了解用例。不要使用shell=True。它不需要调用您的shell。使用p=Popen(['svnadmin','verify','/var/svn/repos/config'],stdout=PIPE,stderr=stdout)instead@S.Lott基本上,svnadmin verify为每个已验证的修订打印一行输出。我想制作一个好的进度指示器,它不会导致过多的产出。有点像wget,例如,这不是旧的pythonio实现中唯一的混乱。这就是为什么Py2.6和Py3k最终得到了一个全新的IO库。如果子进程返回空行,则此代码将中断。更好的解决方案是使用
,而p.poll()为None
而不是
而为True
,如果不是行,则删除
:它工作正常。readline在空行上返回“\n”,其计算结果不为true。它只在管道关闭时返回一个空字符串,这将在子进程终止时返回。@Dave For future ref:print(line.decode('utf-8').rstrip())
,在py2+中打印utf-8行,并告诉python您不需要任何缓冲。亲爱的Python,直接给我输出。下面是如何设置的:您需要设置环境变量 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,) stdout = [] while True: line = p.stdout.readline() if not isinstance(line, (str)): line = line.decode('utf-8') stdout.append(line) print (line) if (line == '' and p.poll() != None): break
 p = subprocess.Popen(cmd,
                         shell=True,
                         cwd=work_dir,
                         bufsize=1,
                         stdin=subprocess.PIPE,
                         stderr=sys.stderr,
                         stdout=sys.stdout)