我有一些Python代码执行外部应用程序,当应用程序输出量很小时可以正常工作,但是当有很多时候会挂起。我的代码如下:
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
errcode = p.wait()
retval = p.stdout.read()
errmess = p.stderr.read()
if errcode:
log.error('cmd failed <%s>: %s' % (errcode,errmess))
文档中的评论似乎表明了潜在的问题。等待,有:
警告:如果子进程生成足够的输出到
stdout
或stderr
管道,这会阻塞等待OS管道缓冲区接受更多数据,这将导致死锁。使用communicate()
来避免这种情况。
虽然在沟通中,我看到了:
注意读取的数据缓冲在内存中,因此如果数据量很大或无限制,请不要使用此方法。
所以我不清楚如果我有大量数据,我应该使用其中任何一种。他们没有说明在这种情况下我应该采用什么方法。
我确实需要exec的返回值并进行解析并同时使用stdout
和stderr
。
那么Python中用于执行具有大输出的外部应用程序的等效方法是什么?
答案 0 :(得分:17)
你正在阻止对两个文件的读取;第一个需要在第二个开始之前完成。如果应用程序向stderr
写了很多内容而对stdout
没有任何内容,那么您的进程将等待stdout
上未发送的数据,而您正在运行的程序正在运行在那里等待它写给stderr
的东西被阅读(它永远不会 - 因为你在等待stdout
)。
有几种方法可以解决这个问题。
最简单的是不拦截stderr
;离开stderr=None
。错误将直接输出到stderr
。您无法拦截它们并将它们显示为您自己的消息的一部分。对于命令行工具,这通常没问题。对于其他应用程序,它可能是一个问题。
另一种简单方法是将stderr
重定向到stdout
,因此您只有一个传入文件:set stderr=STDOUT
。这意味着您无法区分常规输出和错误输出。这可能是也可能是不可接受的,具体取决于应用程序如何写入输出。
处理此问题的完整而复杂的方法是select
(http://docs.python.org/library/select.html)。这使您可以以非阻塞的方式阅读:只要数据出现在stdout
或stderr
,您就会获得数据。如果真的有必要,我只会推荐这个。这可能在Windows中不起作用。
答案 1 :(得分:7)
使用stdout
以非常大的输出(即大量兆字节)独立阅读stderr
和select
:
import subprocess, select
proc = subprocess.Popen(cmd, bufsize=8192, shell=False, \
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
with open(outpath, "wb") as outf:
dataend = False
while (proc.returncode is None) or (not dataend):
proc.poll()
dataend = False
ready = select.select([proc.stdout, proc.stderr], [], [], 1.0)
if proc.stderr in ready[0]:
data = proc.stderr.read(1024)
if len(data) > 0:
handle_stderr_data(data)
if proc.stdout in ready[0]:
data = proc.stdout.read(1024)
if len(data) == 0: # Read of zero bytes means EOF
dataend = True
else:
outf.write(data)
答案 2 :(得分:6)
很多输出是主观的,因此提出建议有点困难。如果输出量确实大,那么您可能不希望通过单个read()调用来获取所有内容。您可能想尝试将输出写入文件,然后逐步提取数据,如下所示:
f=file('data.out','w')
p = subprocess.Popen(cmd, shell=True, stdout=f, stderr=subprocess.PIPE)
errcode = p.wait()
f.close()
if errcode:
errmess = p.stderr.read()
log.error('cmd failed <%s>: %s' % (errcode,errmess))
for line in file('data.out'):
#do something
答案 3 :(得分:6)
使用临时文件的建议可能适用于您,也可能不适用,具体取决于输出的大小等,以及是否需要在生成时处理子进程的输出。
正如Heikki Toivonen建议的那样,你应该看看communicate
方法。但是,这会将子进程的stdout / stderr缓存在内存中,并从communicate
调用中返回 - 这对于某些情况并不理想。但是沟通方法的来源值得一看。
另一个例子是我维护的一个包python-gnupg,其中gpg
可执行文件是通过subprocess
生成来完成繁重的工作,Python包装器生成线程来读取gpg的stdout和stderr并消耗它们作为数据由gpg生成。您也可以通过查看源代码来获得一些想法。在一般情况下,gpg对stdout和stderr生成的数据可能非常大。
答案 4 :(得分:2)
您可以尝试沟通,看看是否能解决您的问题。如果没有,我会将输出重定向到临时文件。
答案 5 :(得分:2)
我遇到了同样的问题。如果你必须处理大输出,另一个好的选择可能是使用stdout和stderr的文件,并为每个参数传递这些文件。
检查python中的tempfile模块:https://docs.python.org/2/library/tempfile.html。
这样的事情可能会起作用
out = tempfile.NamedTemporaryFile(delete=False)
然后你会这样做:
Popen(... stdout=out,...)
然后你可以读取文件,稍后将其删除。
答案 6 :(得分:0)
这是捕获常规输出和错误输出的简单方法,所有这些都在Python中,因此stdout
中的限制不适用:
com_str = 'uname -a'
command = subprocess.Popen([com_str], stdout=subprocess.PIPE, shell=True)
(output, error) = command.communicate()
print output
Linux 3.11.0-20-generic SMP Fri May 2 21:32:55 UTC 2014
和
com_str = 'id'
command = subprocess.Popen([com_str], stdout=subprocess.PIPE, shell=True)
(output, error) = command.communicate()
print output
uid=1000(myname) gid=1000(mygrp) groups=1000(cell),0(root)