如何在获取所需数据后关闭Python 2.5.2的Popen子进程?
我正在使用以下版本的Python:
$ /usr/bin/env python --version
Python 2.5.2
我运行了以下Python代码,目的是将一个子进程中的数据写入标准输出,并把这些数据读入一个叫做metadata
的变量中:
# 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 个回答
这里的信息不够多,无法给出确切的答案,但我可以做一些推测。
首先,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
调用会影响错误。
不过,我看不出你提供的代码会出现这种情况,因为你并没有写入标准输入。虽然这是我能想到的最接近的情况,但没有一个可用的重现示例,可能也只能给你指个方向。
另外,你在删除一个可能不存在的文件之前,不应该检查 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
我觉得这个异常和 subprocess 的调用或者它的文件描述符没有关系(在调用 communicate 后,popen 对象就关闭了)。这似乎是一个经典的问题,就是在管道中关闭了 sys.stdout
。
http://bugs.python.org/issue1596
尽管这个问题已经存在三年了,但仍然没有解决。由于 sys.stdout.write(...)
似乎也没有帮助,你可以尝试使用更底层的调用,试试这个:
os.write(sys.stdout.fileno(), metadata)
嗯,我之前在用子进程和gzip的时候也遇到过一些“断管”的奇怪问题。我一直没搞清楚为什么会这样,但通过改变我的实现方式,我成功避免了这个问题。看起来你只是想用一个后端的gzip进程来解压文件(可能是因为Python自带的模块速度慢得离谱……我也不知道为什么,但确实是这样)。
与其使用 communicate()
,不如把这个进程当作一个完全异步的后端,直接读取它的输出,等数据来了就处理。当这个进程结束时,子进程模块会帮你处理清理工作。下面的代码片段应该能提供相同的基本功能,而且不会出现断管的问题。
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)