将子进程输出显示到标准输出并重定向
- 2025-02-28 08:23:00
- admin 原创
- 63
问题描述:
我正在通过 Python 的 subprocess 模块运行脚本。目前我使用:
p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = p.communicate()
然后我将结果打印到 stdout。这一切都很好,但由于脚本需要很长时间才能完成,我希望脚本的实时输出也能到 stdout。我通过管道传输输出的原因是我想对其进行解析。
解决方案 1:
将子进程的标准输出保存到变量以供进一步处理,并在子进程运行时显示它:
#!/usr/bin/env python3
from io import StringIO
from subprocess import Popen, PIPE
with Popen('/path/to/script', stdout=PIPE, bufsize=1,
universal_newlines=True) as p, StringIO() as buf:
for line in p.stdout:
print(line, end='')
buf.write(line)
output = buf.getvalue()
rc = p.returncode
保存子进程的 stdout 和 stderr 更加复杂,因为您应该同时使用两个流以避免死锁:
stdout_buf, stderr_buf = StringIO(), StringIO()
rc = teed_call('/path/to/script', stdout=stdout_buf, stderr=stderr_buf,
universal_newlines=True)
output = stdout_buf.getvalue()
...
teed_call()
这里定义在哪里。
更新:这是一个更简单的asyncio
版本。
旧版:
child_process.py
以下是基于示例的tulip
单线程解决方案:
import asyncio
import sys
from asyncio.subprocess import PIPE
@asyncio.coroutine
def read_and_display(*cmd):
"""Read cmd's stdout, stderr while displaying them as they arrive."""
# start process
process = yield from asyncio.create_subprocess_exec(*cmd,
stdout=PIPE, stderr=PIPE)
# read child's stdout/stderr concurrently
stdout, stderr = [], [] # stderr, stdout buffers
tasks = {
asyncio.Task(process.stdout.readline()): (
stdout, process.stdout, sys.stdout.buffer),
asyncio.Task(process.stderr.readline()): (
stderr, process.stderr, sys.stderr.buffer)}
while tasks:
done, pending = yield from asyncio.wait(tasks,
return_when=asyncio.FIRST_COMPLETED)
assert done
for future in done:
buf, stream, display = tasks.pop(future)
line = future.result()
if line: # not EOF
buf.append(line) # save for later
display.write(line) # display in terminal
# schedule to read the next line
tasks[asyncio.Task(stream.readline())] = buf, stream, display
# wait for the process to exit
rc = yield from process.wait()
return rc, b''.join(stdout), b''.join(stderr)
该脚本运行'/path/to/script
命令并同时逐行读取其 stdout 和 stderr。这些行会相应地打印到父级的 stdout/stderr 并保存为字节串以供将来处理。要运行read_and_display()
协程,我们需要一个事件循环:
import os
if os.name == 'nt':
loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
asyncio.set_event_loop(loop)
else:
loop = asyncio.get_event_loop()
try:
rc, *output = loop.run_until_complete(read_and_display("/path/to/script"))
if rc:
sys.exit("child failed with '{}' exit code".format(rc))
finally:
loop.close()
解决方案 2:
p.communicate()
等待子进程完成然后立即返回其整个输出。
您是否尝试过这样的方法,逐行读取子进程的输出?
p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for line in p.stdout:
# do something with this individual line
print line
解决方案 3:
Popen.communicate 文档明确指出:
Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
https://docs.python.org/2/library/subprocess.html#subprocess.Popen.communicate
因此,如果您需要实时输出,您需要使用如下方法:
stream_p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while stream_line in stream_p:
#Parse it the way you want
print stream_line
解决方案 4:
这会将 stdout 和 stderr 打印到终端,并将 stdout 和 stderr 保存到变量中:
from subprocess import Popen, PIPE, STDOUT
with Popen(args, stdout=PIPE, stderr=STDOUT, text=True, bufsize=1) as p:
output = "".join([print(buf, end="") or buf for buf in p.stdout])
但是,根据您具体在做什么,可能需要注意这一点:通过使用stderr=STDOUT
,我们无法再区分 stdout 和 stderr,并且通过调用print
,您的输出将始终打印到 stdout,无论它来自 stdout 还是 stderr。
对于 Python <3.7,您将需要使用universal_newlines
而不是text
。
3.7 版新功能:添加了 text 作为 universal_newlines 的更易读的别名。
来源:https ://docs.python.org/3/library/subprocess.html#subprocess.Popen
扫码咨询,免费领取项目管理大礼包!