从 subprocess.communicate() 读取流输入[重复]
- 2024-11-25 08:49:00
- admin 原创
- 159
问题描述:
我正在使用 Pythonsubprocess.communicate()
从运行约一分钟的进程中读取标准输出。
我怎样才能以流式方式打印出该进程的每一行stdout
,以便我可以看到生成的输出,但在继续之前仍然阻止进程终止?
subprocess.communicate()
似乎立即给出了所有输出。
解决方案 1:
在子进程刷新其标准输出缓冲区时逐行获取子进程的输出:
#!/usr/bin/env python2
from subprocess import Popen, PIPE
p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
for line in iter(p.stdout.readline, b''):
print line,
p.wait() # wait for the subprocess to exit
iter()
用于在写入行后立即读取它们,以解决Python 2 中的预读错误。
如果子进程的标准输出在非交互模式下使用块缓冲而不是行缓冲(这会导致输出延迟,直到子进程的缓冲区已满或由子进程明确刷新)那么您可以尝试使用 pexpect
、pty
模块或unbuffer
、、实用程序stdbuf
`script`强制进行无缓冲输出,请参阅问:为什么不直接使用管道(popen())?
以下是 Python 3 代码:
#!/usr/bin/env python3
from subprocess import Popen, PIPE
with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
universal_newlines=True) as p:
for line in p.stdout:
print(line, end='')
注意:与按原样输出子进程的字节串的 Python 2 不同;Python 3 使用文本模式(cmd 的输出使用编码进行解码locale.getpreferredencoding(False)
)。
解决方案 2:
请注意,我认为JF Sebastian 的方法(如下)更好。
这是一个简单的例子(不检查错误):
import subprocess
proc = subprocess.Popen('ls',
shell=True,
stdout=subprocess.PIPE,
)
while proc.poll() is None:
output = proc.stdout.readline()
print output,
如果ls
结束得太快,则 while 循环可能会在读取所有数据之前结束。
您可以通过这种方式在标准输出中捕获余数:
output = proc.communicate()[0]
print output,
解决方案 3:
我相信以流式方式收集流程输出的最简单方法是这样的:
import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
data = proc.stdout.readline() # Alternatively proc.stdout.read(1024)
if len(data) == 0:
break
sys.stdout.write(data) # sys.stdout.buffer.write(data) on Python 3.x
readline()
or函数read()
应仅在进程终止后在 EOF 上返回一个空字符串 - 否则,如果没有内容可读取,它将阻塞(包括换行符,因此在空行上,它返回“\n”)。这避免了在循环后readline()
需要尴尬的最终调用。communicate()
对于具有非常长行的文件,read()
最好减少最大内存使用量 - 传递给它的数字是任意的,但排除它会导致一次读取整个管道输出,这可能是不可取的。
解决方案 4:
如果您想要一种非阻塞方法,请不要使用process.communicate()
。如果将subprocess.Popen()
参数设置stdout
为PIPE
,则可以读取process.stdout
并检查进程是否仍在运行process.poll()
。
解决方案 5:
如果您只是想实时传递输出,那么很难比这更简单:
import subprocess
# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])
请参阅subprocess.check_call() 的文档。
如果你需要处理输出,当然可以循环处理。但如果不需要,就保持简单。
编辑: JF Sebastian指出,stdout 和 stderr 参数的默认值会传递到 sys.stdout 和 sys.stderr,如果 sys.stdout 和 sys.stderr 被替换(例如,用于在测试中捕获输出),则此操作将会失败。
解决方案 6:
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret
,
and
equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:
print(p.stderr.readline().rstrip('
'))
解决方案 7:
添加另一个 python3 解决方案并进行一些小的改动:
允许您捕获 shell 进程的退出代码(我在使用构造时无法获取退出代码
with
)还可以实时将 stderr 输出
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
# Run a shell command, streaming output to STDOUT in real time
# Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
for line in p.stdout:
sys.stdout.write(line)
p.wait()
exit_code = p.returncode
if exit_code != 0 and fail_on_error:
raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
return(exit_code)