Python的Popen清理工作

8

我希望使用Python等效的管道命令来替代Perl。类似于使用Python版本的open(PIPE, "command |")。

我尝试使用subprocess模块实现如下:

p = subprocess.Popen("zgrep thingiwant largefile", shell=True, stdout=subprocess.PIPE)

这种方法可以像在perl中一样读取输出,但它不会自动清理。当我退出解释器时,会出现以下情况:

grep: writing output: Broken pipe

这段代码在stderr上输出了数百万次。我曾天真地希望这一切都能为我解决,但事实并非如此。调用p的terminate或kill似乎没有帮助。查看进程表,可以看到这会杀死/bin/sh进程,但是gzip子进程仍会因为管道损坏而产生错误。

有什么正确的方法来解决这个问题吗?


2
你是否在子进程 p 完成之前退出解释器? - physicsmichael
4个回答

12
问题在于管道已满。子进程停止,等待管道清空,但是您的进程(Python解释器)退出,打破了管道的一端(因此出现错误消息)。

p.wait() 无法帮助您:

警告 如果子进程生成足够多的输出到stdout或stderr管道,以至于它阻塞等待OS管道缓冲区接受更多数据,则会发生死锁。使用 communicate() 来避免这种情况。

http://docs.python.org/library/subprocess.html#subprocess.Popen.wait

p.communicate() 无法帮助您:

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

http://docs.python.org/library/subprocess.html#subprocess.Popen.communicate

p.stdout.read(num_bytes) 无法帮助您:

警告 使用 communicate() 而不是 .stdin.write.stdout.read.stderr.read,以避免由于其他操作系统管道缓冲区填满并阻塞子进程而导致死锁。

http://docs.python.org/library/subprocess.html#subprocess.Popen.stdout

故事的寓意是,对于大量输出,如果程序试图读取数据,则使用 subprocess.PIPE 将使您注定失败(我认为您应该能够将 p.stdout.read(bytes) 放入 while p.returncode is None: 循环中,但上面的警告表明这可能会死锁)。

文档建议使用以下内容替换 shell 管道:

p1 = Popen(["zgrep", "thingiwant", "largefile"], stdout=PIPE)
p2 = Popen(["processreceivingdata"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]

注意,p2 直接从 p1 获取其标准输入。这样应该可以避免死锁,但鉴于上述相互矛盾的警告,谁知道呢。

无论如何,如果最后一部分对您不起作用(尽管它应该有效),您可以尝试创建一个临时文件,将第一次调用的所有数据写入该文件,然后使用临时文件作为下一个进程的输入。


3

打开管道后,您可以使用命令输出:p.stdout

for line in p.stdout:
    # do stuff
p.stdout.close()

0
你是如何执行这个过程的?
正确的方法是使用:
p.communicate()

更多细节请参阅文档。


即使我从未与该进程通信,也会发生这种情况。仅创建对象p然后退出解释器就会导致此问题。 - pythonic metaphor
是的,如果我没记错的话,Popen执行命令。communicate()然后等待进程结束,缓冲区被刷新等等。还可以看看check_call() - Almad

0
你需要等待进程完成:

wait

import subprocess
p = subprocess.Popen("cat /mach_kernel", shell=True)
p.wait()

或者,您可以捕获程序的标准输出(就像您所做的那样),也许还有它的标准错误,并调用communicate

import subprocess
p = subprocess.Popen("cat /mach_kernel", shell=True,
                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接