一旦有了我需要的数据,如何关闭Python 2.5.2 Popen子进程?

时间:2010-10-05 05:42:05

标签: python pipe popen

我正在运行以下版本的Python:

$ /usr/bin/env python --version                                                                                                                                                            
Python 2.5.2                                    

我运行以下Python代码将数据从子子进程写入标准输出,并将其读入名为metadata的Python变量中:

# Extract metadata (snippet from extractMetadata.py)
inFileAsGzip = "%s.gz" % inFile                                                                                                                                                                                                            
if os.path.exists(inFileAsGzip):                                                                                                                                                                                                           
    os.remove(inFileAsGzip)                                                                                                                                                                                                                
os.symlink(inFile, inFileAsGzip)                                                                                                                                                                                                           
extractMetadataCommand = "bgzip -c -d -b 0 -s %s %s" % (metadataRequiredFileSize, inFileAsGzip)                                                                                                                                            
metadataPipes = subprocess.Popen(extractMetadataCommand, stdin=None, stdout=subprocess.PIPE, shell=True, close_fds=True)                                                                                                      
metadata = metadataPipes.communicate()[0]                                                                                                                                                                                                                                                                                                                                                                                                          
metadataPipes.stdout.close()                                                                                                                                                                                                             
os.remove(inFileAsGzip) 
print metadata

用例如下,从前面提到的代码片段中拉出前十行标准输出:

$ extractMetadata.py | head

如果我输入head,awk,grep等,则会出现错误

脚本以以下错误结束:

close failed: [Errno 32] Broken pipe

我原本以为关闭管道就足够了,但显然事实并非如此。

4 个答案:

答案 0 :(得分:4)

嗯。我之前看到过subprocess + gzip的一些“Broken pipe”奇怪之处。我从来没有弄明白为什么会发生这种情况,但通过改变我的实施方法,我能够避免这个问题。看起来你只是想尝试使用后端gzip进程来解压缩文件(可能是因为Python的内置模块非常慢......不知道为什么,但绝对是这样)。

而不是使用communicate(),您可以将进程视为完全异步的后端,并在它到达时读取它的输出。当进程终止时,子进程模块将负责为您清理。以下snippit应提供相同的基本功能,而不会出现任何管道问题。

import subprocess

gz_proc = subprocess.Popen(['gzip', '-c', '-d', 'test.gz'], stdout=subprocess.PIPE)

l = list()
while True:
    dat = gz_proc.stdout.read(4096)
    if not d:
        break
    l.append(d)

file_data = ''.join(l)

答案 1 :(得分:1)

我认为此异常与子进程调用及其文件描述符无关(在调用 communication 之后 popen 对象已关闭) 。这似乎是在管道中关闭sys.stdout的经典问题:

http://bugs.python.org/issue1596

尽管是一只3岁的虫子,但还没有解决。由于sys.stdout.write(...)似乎也没有帮助,您可以采用较低级别的电话,试试这个:

os.write(sys.stdout.fileno(), metadata)

答案 2 :(得分:0)

没有足够的信息可以最终回答这个问题,但我可以做一些有根据的猜测。

首先,os.remove绝对不应该与EPIPE失败。它看起来也不像;错误为close failed: [Errno 32] Broken pipe,而不是remove failed。看起来close失败了,而不是remove

关闭管道的标准输出可能会产生此错误。如果数据被缓冲,Python将在关闭文件之前刷新数据。如果底层进程消失,执行此操作将引发IOError / EPIPE。但请注意,这不是致命错误:即使发生这种情况,文件仍然关闭。以下代码在大约50%的时间内重现了这一点,并证明该文件在异常后关闭。 (注意;我认为bufsize的行为已经在不同版本中发生了变化。)

    import os, subprocess
    metadataPipes = subprocess.Popen("echo test", stdin=subprocess.PIPE,
        stdout=subprocess.PIPE, shell=True, close_fds=True, bufsize=4096)
    metadataPipes.stdin.write("blah"*1000)
    print metadataPipes.stdin
    try:
        metadataPipes.stdin.close()
    except IOError, e:
        print "stdin after failure: %s" % metadataPipes.stdin

这很生气;它只发生在一部分时间。这可以解释为什么删除或添加os.remove调用会影响错误。

那就是说,我看不出你提供的代码会怎么样,因为你没有写入stdin。不过,如果没有可用的复制品,这是我能得到的最接近的,也许它会指向正确的方向。

作为旁注,在删除可能不存在的文件之前,不应检查os.path.exists;如果另一个进程同时删除该文件,它将导致竞争条件。相反,这样做:

try:
    os.remove(inFileAsGzip)
except OSError, e:
    if e.errno != errno.ENOENT: raise

...我通常用rm_f这样的函数包装。

最后,如果你明确想要杀死一个子进程,那就是metadataPipes.kill - 只是关闭它的管道就不会那么做 - 但这无助于解释错误。另外,如果您只是阅读gzip文件,那么使用gzip模块比使用子进程要好得多。 http://docs.python.org/library/gzip.html

答案 3 :(得分:0)

从流程输出中获取前10行可能会更好地运行:

ph = os.popen(cmdline, 'r')
lines = []
for s in ph:
    lines.append(s.rstrip())
    if len(lines) == 10: break
print '\n'.join(lines)
ph.close()