我正试图从stdout
电话中抓取subprocess.Popen
,虽然我通过以下方式轻松实现此目标:
cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE)
for line in cmd.stdout.readlines():
print line
我想在“实时”中抓住stdout
。使用上面的方法,PIPE正在等待抓取所有stdout
,然后它返回。
因此,出于记录目的,这不符合我的要求(例如,“看到”发生时会发生什么)。
有没有办法在运行时逐行获取stdout
?或者这是subprocess
的限制(必须等到PIPE
关闭)。
修改的
如果我为readlines()
切换readline()
,我只会获得stdout
的最后一行(不理想):
In [75]: cmd = Popen('ls -l', shell=True, stdout=PIPE)
In [76]: for i in cmd.stdout.readline(): print i
....:
t
o
t
a
l
1
0
4
答案 0 :(得分:19)
你的翻译正在缓冲。在print语句后添加对sys.stdout.flush()的调用。
答案 1 :(得分:12)
实际上,真正的解决方案是直接将子流程的stdout重定向到流程的stdout。
实际上,使用您的解决方案,您只能同时打印stdout,而不能打印stderr。
import sys
from subprocess import Popen
Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate()
communicate()
是为了使调用阻塞直到子进程结束,否则它将直接转到下一行,程序可能会在子进程之前终止(尽管重定向到stdout仍然有效) ,即使你的python脚本关闭后,我测试了它。)
这样,例如,您将以绝对实时方式重定向stdout和stderr。
例如,就我而言,我使用此脚本slow_cmd_output.sh
进行了测试:
#!/bin/bash
for i in 1 2 3 4 5 6; do sleep 5 && echo "${i}th output" && echo "err output num ${i}" >&2; done
答案 2 :(得分:11)
要“实时”输出,subprocess
不合适,因为它无法击败其他进程的缓冲策略。这就是我总是推荐的原因,每当需要这样的“实时”输出抓取时(非常频繁的堆栈溢出问题!),改为使用pexpect(除了Windows之外的所有地方 - 在Windows上,wexpect )。
答案 3 :(得分:3)
删除正在合并输出的readlines()。 此外,您还需要强制执行行缓冲,因为大多数命令都会将输出缓冲到管道。有关详细信息,请参阅:http://www.pixelbeat.org/programming/stdio_buffering/
答案 4 :(得分:3)
由于这是一个问题,我搜索了几天的答案,我想把这个留给那些跟随的人。虽然subprocess
确实无法对抗其他进程的缓冲策略,但在使用subprocess.Popen
调用另一个Python脚本的情况下,可以告诉它启动一个无缓冲的python。
command = ["python", "-u", "python_file.py"]
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in iter(p.stdout.readline, ''):
line = line.replace('\r', '').replace('\n', '')
print line
sys.stdout.flush()
我也看到了popen参数bufsize=1
和universal_newlines=True
帮助揭露隐藏的stdout
的情况。
答案 5 :(得分:1)
cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
print line.rstrip("\n")
答案 6 :(得分:0)
对readlines
的调用正在等待进程退出。将其替换为围绕cmd.stdout.readline()
的循环(注意单数),一切都应该很好。
答案 7 :(得分:0)
如上所述,问题在于当没有终端连接到进程时stdio库缓冲了printf like语句。无论如何,在Windows平台上有一种解决方法。其他平台上也可能有类似的解决方案。
在Windows上,您可以在创建流程时强制创建新控制台。好处是它可以保持隐藏,所以你永远不会看到它(这是由子进程模块中的shell = True完成的)。
cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE, creationflags=_winapi.CREATE_NEW_CONSOLE, bufsize=1, universal_newlines=True)
for line in cmd.stdout.readlines():
print line
或
稍微更完整的解决方案是您明确设置了STARTUPINFO参数,这会阻止启动一个新的和不必要的cmd.exe shell进程,上面有shell = True。
class PopenBackground(subprocess.Popen):
def __init__(self, *args, **kwargs):
si = kwargs.get('startupinfo', subprocess.STARTUPINFO())
si.dwFlags |= _winapi.STARTF_USESHOWWINDOW
si.wShowWindow = _winapi.SW_HIDE
kwargs['startupinfo'] = si
kwargs['creationflags'] = kwargs.get('creationflags', 0) | _winapi.CREATE_NEW_CONSOLE
kwargs['bufsize'] = 1
kwargs['universal_newlines'] = True
super(PopenBackground, self).__init__(*args, **kwargs)
process = PopenBackground(['ls', '-l'], stdout=subprocess.PIPE)
for line in cmd.stdout.readlines():
print line