如何同时观察长时间运行的子进程的标准输出和标准错误,一旦子进程生成就处理每一行?
我不介意使用Python3.6的异步工具来制作我希望在两个流中的每个流上的非阻塞异步循环,但这似乎无法解决问题。以下代码:
import asyncio
from asyncio.subprocess import PIPE
from datetime import datetime
async def run(cmd):
p = await asyncio.create_subprocess_shell(cmd, stdout=PIPE, stderr=PIPE)
async for f in p.stdout:
print(datetime.now(), f.decode().strip())
async for f in p.stderr:
print(datetime.now(), "E:", f.decode().strip())
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(run('''
echo "Out 1";
sleep 1;
echo "Err 1" >&2;
sleep 1;
echo "Out 2"
'''))
loop.close()
输出:
2018-06-18 00:06:35.766948 Out 1
2018-06-18 00:06:37.770187 Out 2
2018-06-18 00:06:37.770882 E: Err 1
虽然我希望它能输出如下内容:
2018-06-18 00:06:35.766948 Out 1
2018-06-18 00:06:36.770882 E: Err 1
2018-06-18 00:06:37.770187 Out 2
答案 0 :(得分:6)
要完成此任务,您需要一个将采用两个异步序列并合并它们的函数,从而产生其中一个或另一个的结果,因为它们变得可用。有了库存这样的功能,run
看起来像这样:
async def run(cmd):
p = await asyncio.create_subprocess_shell(cmd, stdout=PIPE, stderr=PIPE)
async for f in merge(p.stdout, p.stderr):
print(datetime.now(), f.decode().strip())
merge
之类的功能(尚未)存在于标准库中,但aiostream
外部库provides one。您也可以使用异步生成器和asyncio.wait()
编写自己的代码:
async def merge(*iterables):
iter_next = {it.__aiter__(): None for it in iterables}
while iter_next:
for it, it_next in iter_next.items():
if it_next is None:
fut = asyncio.ensure_future(it.__anext__())
fut._orig_iter = it
iter_next[it] = fut
done, _ = await asyncio.wait(iter_next.values(),
return_when=asyncio.FIRST_COMPLETED)
for fut in done:
iter_next[fut._orig_iter] = None
try:
ret = fut.result()
except StopAsyncIteration:
del iter_next[fut._orig_iter]
continue
yield ret
上面的run
仍然会在一个细节上与您想要的输出不同:它不会区分输出和错误行。但这可以通过用指标装饰线条来轻松实现:
async def decorate_with(it, prefix):
async for item in it:
yield prefix, item
async def run(cmd):
p = await asyncio.create_subprocess_shell(cmd, stdout=PIPE, stderr=PIPE)
async for is_out, line in merge(decorate_with(p.stdout, True),
decorate_with(p.stderr, False)):
if is_out:
print(datetime.now(), line.decode().strip())
else:
print(datetime.now(), "E:", line.decode().strip())
答案 1 :(得分:0)
在我看来,实际上有一个更简单的解决方案,至少在监视代码不需要单独进行协程调用的情况下如此。
您可以做的是生成两个单独的协程,一个用于stdout,一个用于stderr。并行运行它们将为您提供所需的语义,您可以使用gather
等待其完成:
def watch(stream, prefix=''):
async for line in stream:
print(datetime.now(), prefix, line.decode().strip())
async def run(cmd):
p = await asyncio.create_subprocess_shell(cmd, stdout=PIPE, stderr=PIPE)
await asyncio.gather(watch(p.stdout), watch(p.stderr, 'E:'))