python - 使用 Python 子进程增量显示进程输出
问题描述
我正在尝试从 Python 自动化脚本中运行“docker-compose pull”,并逐渐显示 Docker 命令在直接从 shell 运行时将打印的相同输出。此命令为系统中找到的每个 Docker 映像打印一行,使用 Docker 映像的下载进度(百分比)增量更新每一行,并在下载完成时将此百分比替换为“完成”。我首先尝试使用 subprocess.poll() 和(阻塞)readline() 调用获取命令输出:
import shlex
import subprocess
def run(command, shell=False):
p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=shell)
while True:
# print one output line
output_line = p.stdout.readline().decode('utf8')
error_output_line = p.stderr.readline().decode('utf8')
if output_line:
print(output_line.strip())
if error_output_line:
print(error_output_line.strip())
# check if process finished
return_code = p.poll()
if return_code is not None and output_line == '' and error_output_line == '':
break
if return_code > 0:
print("%s failed, error code %d" % (command, return_code))
run("docker-compose pull")
代码卡在第一个(阻塞)readline() 调用中。然后我尝试在不阻塞的情况下做同样的事情:
import select
import shlex
import subprocess
import sys
import time
def run(command, shell=False):
p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=shell)
io_poller = select.poll()
io_poller.register(p.stdout.fileno(), select.POLLIN)
io_poller.register(p.stderr.fileno(), select.POLLIN)
while True:
# poll IO for output
io_events_list = []
while not io_events_list:
time.sleep(1)
io_events_list = io_poller.poll(0)
# print new output
for event in io_events_list:
# must be tested because non-registered events (eg POLLHUP) can also be returned
if event[1] & select.POLLIN:
if event[0] == p.stdout.fileno():
output_str = p.stdout.read(1).decode('utf8')
print(output_str, end="")
if event[0] == p.stderr.fileno():
error_output_str = p.stderr.read(1).decode('utf8')
print(error_output_str, end="")
# check if process finished
# when subprocess finishes, iopoller.poll(0) returns a list with 2 select.POLLHUP events
# (one for stdout, one for stderr) and does not enter in the inner loop
return_code = p.poll()
if return_code is not None:
break
if return_code > 0:
print("%s failed, error code %d" % (command, return_code))
run("docker-compose pull")
这可行,但是当所有 Docker 映像下载完成后,只有最后几行(末尾带有“完成”)会打印到屏幕上。
这两种方法都适用于具有更简单输出的命令,例如“ls”。也许问题与这个 Docker 命令如何逐步打印到屏幕上,覆盖已经写好的行有关?通过 Python 脚本运行命令时,是否有一种安全的方法可以在命令行中逐步显示命令的确切输出?
编辑:第二个代码块已更正
解决方案
始终将 STDIN 作为管道打开,如果您不使用它,请立即关闭它。
p.stdout.read() 将阻塞直到管道关闭,因此您的轮询代码在这里没有任何用处。它需要修改。
我建议不要使用 shell=True
代替 *.readline(),尝试使用 *.read(1) 并等待“\n”
当然你可以在 Python 中做你想做的事,问题是如何做。因为,子进程可能对其输出的外观有不同的想法,这就是麻烦开始的时候。例如,进程可能希望在另一端明确地有一个终端,而不是您的进程。或者很多这样简单的废话。此外,缓冲也可能导致问题。您可以尝试以无缓冲模式启动 Python 进行检查。(/usr/bin/python -U)
如果没有任何效果,则使用 pexpect 自动化库而不是子进程。
推荐阅读
- javascript - 单元测试 Javascript Ajax 调用
- reactjs - 当使用 immutable.js 和 react redux 时,是否允许 reducer 返回记录实例而不是对象?
- sql - SQL 以多个值结尾
- c++ - Clang vs Clang tidy 是否检测到相同的警告和错误?
- python - 在脚本期间刷新 Maya UI?
- python - 在调用期间修补 pathlib 并测试字符串
- java - XPath:查找具有两个条件的节点
- azure - 在 Azure DSVM 上创建和使用自定义 Anaconda 环境
- mysql - 在 where 子句中使用 case 语句或构造动态查询是否更好
- javascript - 如何在 Google Maps api 上更新或强制重绘标记集群图标?