实时 subprocess.Popen 通过 stdout 和 PIPE
Posted
技术标签:
【中文标题】实时 subprocess.Popen 通过 stdout 和 PIPE【英文标题】:real time subprocess.Popen via stdout and PIPE 【发布时间】:2011-01-06 04:09:03 【问题描述】:我正在尝试从subprocess.Popen
呼叫中获取stdout
,尽管我很容易通过以下方式实现:
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
【问题讨论】:
复制:***.com/questions/1822237/… 相关:Python: read streaming input from subprocess.communicate() 使用readline()
,您将得到第一行,而不是最后一行。您必须反复调用readline()
才能获取每一行。
相关:Python subprocess readlines() hangs
【参考方案1】:
如前所述,当没有终端连接到进程时,问题在于 stdio 库对类似 printf 语句的缓冲。无论如何,在 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
【讨论】:
child 的缓冲策略无关紧要,只要您使用直到 EOF 才会返回的.readlines()
。请改用for line in iter(cmd.stdout.readline, b''):
。 Read my answers to the questions I've linked above【参考方案2】:
其实真正的解决方案是直接将子进程的stdout重定向到你进程的stdout。
确实,使用您的解决方案,您只能同时打印 stdout,而不能同时打印 stderr。
import sys
from subprocess import Popen
Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate()
communicate()
是为了使调用阻塞直到子进程结束,否则它将直接转到下一行,并且您的程序可能会在子进程之前终止(尽管重定向到您的标准输出仍然有效,即使在您的 python 脚本关闭后,我对其进行了测试)。
这样,例如,您正在重定向 stdout 和 stderr,并且是绝对实时的。
例如,在我的情况下,我使用此脚本进行了测试 slow_cmd_output.sh
:
#!/bin/bash
for i in 1 2 3 4 5 6; do sleep 5 && echo "$ith output" && echo "err output num $i" >&2; done
【讨论】:
注意:我知道这个帖子很旧,但我遇到了这个帖子,答案并不让我满意。找到答案后,我想我不妨把它贴出来:) 这有什么陷阱吗? @撤消【参考方案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
。
【讨论】:
您可以使用print line,
(注意:逗号)来避免删除换行符(不需要replace('\n','')
或rstrip(b'\r\n')
)。【参考方案4】:
您的解释器正在缓冲。在您的打印语句之后添加对 sys.stdout.flush() 的调用。
【讨论】:
太棒了!谢谢!这就是它!,我已经放弃了......这非常有效。再次感谢!呜呼! @alfredodeza:它不可能工作.readlines()
在读取 所有输出 之前不会返回(直到 EOF 发生),因此添加 sys.stdout.flush()
不会改变任何东西。它与您的要求相矛盾:“有没有办法在 [子进程] 运行时逐行获取标准输出?”【参考方案5】:
要“实时”获得输出,subprocess
不适合,因为它无法击败其他进程的缓冲策略。这就是我一直建议的原因,每当需要这种“实时”输出抓取(关于堆栈溢出的一个常见问题!),改为使用pexpect(除 Windows 之外的任何地方——在 Windows 上,wexpect)。
【讨论】:
Expect 的行缓冲是我(最近)深爱的一个话题;你介意看看我的最新问题吗? @Tobu,当然,看过并回答(再次推荐 pexpect)。winpexpect
module 也可以在 Windows 上使用。
你也可以provide a pseudo-tty even while using subprocess
module【参考方案6】:
删除合并输出的 readlines()。 此外,您还需要强制执行行缓冲,因为大多数命令将内部缓冲输出到管道。详情见:http://www.pixelbeat.org/programming/stdio_buffering/
【讨论】:
我浏览了链接,但我不清楚如何在 Python 中强制缓冲,你能澄清一下吗? 您对命令强制执行缓冲。 tail -f 默认为行缓冲区。对于 grep、sed 等,您需要将适当的选项传递给它们。还要注意新的 stdbuf 命令,它可以将行缓冲应用于任何使用 stdio 的命令【参考方案7】:cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
print line.rstrip("\n")
【讨论】:
罗杰,这仍在等待过程结束。我可以确认这种情况正在发生的方法是运行更长的过程,例如系统更新并将信息传递到日志。 10 秒运行过程中的所有行同时打印/写入日志 您遇到了缓冲区大小问题;使用我上面的代码并将命令更改为["find", "/"]
,您将在该过程结束之前看到输出。
我print(line.rstrip("\n"))
在Python3中得到了TypeError: a bytes-like object is required, not 'str'
,你有什么想法吗?【参考方案8】:
对readlines
的调用正在等待进程退出。用 cmd.stdout.readline()
周围的循环替换它(注意单数),一切都应该很好。
【讨论】:
ths 只返回最后一行,而不是所有行:for i in cmd.stdout.readline(): print i ....: t o t a l 1 0 4 是的,我的错。正确答案是上面罗伯特·佩特的答案。 Robert Pate 给出了答案(至少搜索 Robert,一无所获)。虽然你的答案不正确,但你应该删除它......以上是关于实时 subprocess.Popen 通过 stdout 和 PIPE的主要内容,如果未能解决你的问题,请参考以下文章
Subprocess.Popen:将 stdout 和 stderr 克隆到终端和变量
使用 subprocess.Popen 通过 SSH 或 SCP 发送密码
通过 subprocess.Popen 在 python 中执行 R 脚本