繁体   English   中英

实时读取标准输出过程

[英]Reading stdout process in real time

让我们考虑以下代码段:

from subprocess import Popen, PIPE, CalledProcessError


def execute(cmd):
    with Popen(cmd, shell=True, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
        for line in p.stdout:
            print(line, end='')

    if p.returncode != 0:
        raise CalledProcessError(p.returncode, p.args)

base_cmd = [
    "cmd", "/c", "d:\\virtual_envs\\py362_32\\Scripts\\activate",
    "&&"
]
cmd1 = " ".join(base_cmd + ['python -c "import sys; print(sys.version)"'])
cmd2 = " ".join(base_cmd + ["python -m http.server"])

如果我运行execute(cmd1)则输出将没有任何问题。

但是,如果我运行execute(cmd2)则什么也不会打印,这是为什么以及如何修复它,以便可以实时查看http.server的输出。

另外,如何for line in p.stdout内部评估for line in p.stdout 直到达到stdout eof还是某种无穷循环?

这样的主题已经在SO中解决了几次,但是我还没有找到Windows解决方案。 上面的代码片段实际上是此答案中的代码,并试图从virtualenv运行http.server(在win7上为python3.6.2-32bits)

使用此代码,由于缓冲,您将看不到实时输出:

for line in p.stdout:
    print(line, end='')

但是,如果您使用p.stdout.readline()它应该可以工作:

while True:
  line = p.stdout.readline()
  if not line: break
  print(line, end='')

有关详细信息,请参见相应的python错误讨论

UPD:在这里,您可以在stackoverflow的各种解决方案中发现几乎相同的问题

如果要从正在运行的子进程中连续读取,则必须使进程的输出无缓冲。 您的子进程是Python程序,可以通过将-u传递给解释器来完成:

python -u -m http.server

这就是Windows框上的外观。

在此处输入图片说明

如何在内部评估p.stdout中的行? 直到达到stdout eof还是某种无穷循环?

p.stdout是一个缓冲区(阻塞)。 缓冲区读取数据时,您将被阻塞,直到有内容写入该缓冲区。 一旦其中包含某些内容,就可以获取数据并执行内部部分。

考虑一下tail -f在linux上如何工作:它等待直到将某些内容写入文件,然后在它执行时将新数据回显到屏幕上。 没有数据怎么办? 它等待。 因此,当您的程序到达这一行时,它将等待数据并对其进行处理。

由于您的代码有效,但是当不能作为模型运行时,它必须以某种方式与此相关。 http.server模块可能会缓冲输出。 尝试向Python添加-u参数以无缓冲方式运行该进程:

-u:无缓冲的二进制stdout和stderr; 也PYTHONUNBUFFERED = x有关与'-u'有关的内部缓冲的详细信息,请参见手册页

另外,您可能希望尝试将循环更改for line in iter(lambda: p.stdout.read(1), ''): ,因为这在处理之前每次读取1个字节。


更新 :完整的循环代码是

for line in iter(lambda: p.stdout.read(1), ''):
    sys.stdout.write(line)
    sys.stdout.flush()

另外,您将命令作为字符串传递。 尝试将其作为列表传递,每个元素都位于其自己的插槽中:

cmd = ['python', '-m', 'http.server', ..]

我认为主要问题是http.server以某种方式将输出记录到stderr ,在这里我有一个使用asyncio的示例,从stdoutstderr读取数据。

我的第一次尝试是使用asyncio,这是一个不错的API,自Python 3.4起就存在。 后来我找到了一个更简单的解决方案,因此您可以选择,两个em都应该起作用。

异步解决方案

在后台,asyncio使用的是IOCP -Windows API进行异步处理。

# inspired by https://pymotw.com/3/asyncio/subprocesses.html

import asyncio
import sys
import time

if sys.platform == 'win32':
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)

async def run_webserver():
    buffer = bytearray()

    # start the webserver without buffering (-u) and stderr and stdin as the arguments
    print('launching process')
    proc = await asyncio.create_subprocess_exec(
        sys.executable, '-u', '-mhttp.server',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )

    print('process started {}'.format(proc.pid))
    while 1:
        # wait either for stderr or stdout and loop over the results
        for line in asyncio.as_completed([proc.stderr.readline(), proc.stdout.readline()]):
            print('read {!r}'.format(await line))

event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(run_df())
finally:
    event_loop.close()

从标准输出重定向

根据您的示例,这是一个非常简单的解决方案。 它只是将stderr重定向到stdout,并且仅读取stdout。

from subprocess import Popen, PIPE, CalledProcessError, run, STDOUT import os

def execute(cmd):
    with Popen(cmd, stdout=PIPE, stderr=STDOUT, bufsize=1) as p:
        while 1:
            print('waiting for a line')
            print(p.stdout.readline())

cmd2 = ["python", "-u", "-m", "http.server"]

execute(cmd2)

您可以在操作系统级别实现无缓冲区行为。

在Linux中,您可以使用stdbuf包装现有的命令行:

stdbuf -i0 -o0 -e0 YOURCOMMAND

或者在Windows中,您可以使用winpty包装现有的命令行:

winpty.exe -Xallow-non-tty -Xplain YOURCOMMAND

我不知道与操作系统无关的工具。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM