我正在运行以下版本的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
我原本以为关闭管道就足够了,但显然事实并非如此。
答案 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()