使用subprocess.Popen进行大输出的进程

时间:2009-07-24 23:08:08

标签: python subprocess

我有一些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))

文档中的评论似乎表明了潜在的问题。等待,有:

  

警告:如果子进程生成足够的输出到stdoutstderr管道,这会阻塞等待OS管道缓冲区接受更多数据,这将导致死锁。使用communicate()来避免这种情况。

虽然在沟通中,我看到了:

注意读取的数据缓冲在内存中,因此如果数据量很大或无限制,请不要使用此方法。

所以我不清楚如果我有大量数据,我应该使用其中任何一种。他们没有说明在这种情况下我应该采用什么方法。

我确实需要exec的返回值并进行解析并同时使用stdoutstderr

那么Python中用于执行具有大输出的外部应用程序的等效方法是什么?

7 个答案:

答案 0 :(得分:17)

你正在阻止对两个文件的读取;第一个需要在第二个开始之前完成。如果应用程序向stderr写了很多内容而对stdout没有任何内容,那么您的进程将等待stdout上未发送的数据,而您正在运行的程序正在运行在那里等待它写给stderr的东西被阅读(它永远不会 - 因为你在等待stdout)。

有几种方法可以解决这个问题。

最简单的是不拦截stderr;离开stderr=None。错误将直接输出到stderr。您无法拦截它们并将它们显示为您自己的消息的一部分。对于命令行工具,这通常没问题。对于其他应用程序,它可能是一个问题。

另一种简单方法是将stderr重定向到stdout,因此您只有一个传入文件:set stderr=STDOUT。这意味着您无法区分常规输出和错误输出。这可能是也可能是不可接受的,具体取决于应用程序如何写入输出。

处理此问题的完整而复杂的方法是selecthttp://docs.python.org/library/select.html)。这使您可以以非阻塞的方式阅读:只要数据出现在stdoutstderr,您就会获得数据。如果真的有必要,我只会推荐这个。这可能在Windows中不起作用。

答案 1 :(得分:7)

使用stdout以非常大的输出(即大量兆字节)独立阅读stderrselect

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)

Glenn Maynard对于僵局的评论是正确的。但是,解决这个问题的最好方法是创建两个线程,一个用于stdout,一个用于stderr,它们读取相应的流直到用完为止,并根据输出执行任何操作。

使用临时文件的建议可能适用于您,也可能不适用,具体取决于输出的大小等,以及是否需要在生成时处理子进程的输出。

正如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)