实时读取stdout进程

让我们考虑这个片段:

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进行评估? 直到达到标准输出或某种东西,它是一种无尽的循环?

这个话题已经在这里讨论了几次,但我还没有find一个Windows解决scheme。 上面的代码实际上是代码从这个答案,并试图从virtualenv(Win7上的python3.6.2-32bits)运行http.server

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

python -u -m http.server

这是它在Windows机器上的外观。

在这里输入图像说明

使用你的代码,你不能看到实时输出,因为缓冲^

 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 bug讨论

UPD:在这里你可以找到几乎相同的问题与各种解决方案在stackoverflow。

如何对p.stdout中的行进行内部评估? 直到达到标准输出或某种东西,它是一种无尽的循环?

p.stdout是一个缓冲区(阻塞)。 当你从一个空的缓冲区读取时,你将被阻塞,直到有东西写入到这个缓冲区。 一旦有东西在里面,你得到的数据和执行内部的一部分。

想想linux下的tail -f是如何工作的:等到写入文件的东西被写入之后,再把新的数据回显到屏幕上。 当没有数据时会发生什么? 它等待。 所以当你的程序到达这条线时,它会等待数据并处理它。

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

-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以来就存在。 后来我发现了一个更简单的解决方案,所以你可以选择,他们都应该工作。

asyncio作为解决方案

在后台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 

我不知道这种操作系统中立的工具。