仅针对大文件从python调用zgrep系统后出错

仅针对大文件从python调用zgrep系统后出错,python,grep,subprocess,Python,Grep,Subprocess,我正在使用python脚本对zgrep进行系统调用,并且仅使用-m1选项打印第一个结果 脚本: 错误: 在大文件(+2MB)上运行脚本时,会生成以下错误 ./break-zgrep.py gzip:stdout:管道破裂 回溯(最近一次呼叫最后一次): 文件“/breaked zgrep.py”,第25行,在 打印子流程。检查输出(“zgrep-m1'a'test.txt.gz”,shell=True) 检查输出中的文件“/usr/intel/pkgs/python/2.7/lib/python

我正在使用python脚本对
zgrep
进行系统调用,并且仅使用
-m1
选项打印第一个结果

脚本: 错误: 在大文件(+2MB)上运行脚本时,会生成以下错误

./break-zgrep.py
gzip:stdout:管道破裂
回溯(最近一次呼叫最后一次):
文件“/breaked zgrep.py”,第25行,在
打印子流程。检查输出(“zgrep-m1'a'test.txt.gz”,shell=True)
检查输出中的文件“/usr/intel/pkgs/python/2.7/lib/python2.7/subprocess.py”,第537行
引发被调用的进程错误(retcode,cmd,output=output)
subprocess.CalledProcessError:命令'zgrep-m1'a'test.txt.gz'返回非零退出状态2
但是,如果我复制python抱怨的命令并直接在shell中运行它,它就可以正常工作

>zgrep-m1'a'test.txt.gz
0000000 8c82 524d 67a4 c37d 0595 a457 b110 3192
在shell中手动运行后,命令的退出状态为
0
,表示成功。Python说命令退出时出现错误代码
2

echo$? 0 下面是如何制作一个示例测试文件来重现错误。它创建一个100000行的随机变量十六进制文件,并使用
gzip
对其进行压缩

cat/dev/uradom | hextump | head-n100000 | gzip>test.txt.gz

看似无关的更改将防止错误:
  • 制作一个较小的测试文件

    cat/dev/uradom | hextump | head-n100 | gzip>test.txt.gz

  • 在没有
    -m1
    选项的情况下运行(警告:将发送垃圾邮件)

    打印子流程。检查输出(“zgrep'a'test.txt.gz”,shell=True)

  • 在未压缩文件上使用
    grep
    而不是
    zgrep

    cat/dev/uradom | hextump | head-n 100000>test.txt

    打印子流程。检查输出(“grep-m1'a'test.txt”,shell=True)

  • perl

    perl-e'print`zgrep-m1'a'test.txt.gz`


我不知道为什么
python
zgrep
-m
选项和大文件的组合会产生这个错误。如果消除了这些因素中的任何一个,那么就没有错误

我对原因的最佳猜测是通过阅读
grep
man
页面中关于
-m
选项的内容

-m NUM,--max count=NUM
在NUM匹配行之后停止读取文件。如果输入是
来自常规文件的标准输入和NUM匹配行
输出时,grep确保将标准输入定位到
仅在退出前的最后一个匹配行之后,无论
尾部上下文行的存在。这将启用呼叫
恢复搜索的过程。当grep在NUM匹配后停止时
行,它输出任何后续上下文行。
我最初假设
-m
选项只会导致
grep
在找到NUM匹配项后退出。但是,也许在
grep
和标准输入中有一些有趣的地方。这仍然不能解释为什么这个错误只适用于大型压缩文件


我最终将我的脚本从python移植到perl来解决这个问题,所以现在不需要任何解决方案。但我真的很想更好地理解为什么这场完美风暴失败了。

zgrep只是一个shell脚本,它大致相当于
gunzip test.txt.gz | grep-m1'a'
。gunzip只提取块并将其传递给grep。然后,当grep找到模式时,它退出

如果gunzip到那时还没有完成对文件的解压缩,那么将来对gunzip的stdout(连接到grep的stdin)的写入将失败。这正是你的情况:

gzip: stdout: Broken pipe

感谢MilesF,本文完美地解释了这一点:

python代码应更改为:

import subprocess
import signal

print subprocess.check_output("zgrep -m1 'a' test.txt.gz", shell=True, , preexec_fn=lambda:signal.signal(signal.SIGPIPE, signal.SIG_DFL))

那么Perl有什么不同呢?Perl会抑制错误消息吗?嗯,我不知道为什么。通常,首先不应该发生
stdout:breaked pipe
错误。这涉及到大量的缓冲,这会使事情变得奇怪(例如,较大的文件和更快/更慢的磁盘的行为不同)。啊,这就解释了为什么它只发生在较大的文件上。我仍然很好奇python是否有办法忽略生成的错误,以及为什么在shell中手动运行该命令会产生0的退出代码。请注意,如果重新搜索(“a”,ln)),可以使用类似于
itertools.islice((ln for ln in gzip.open(“test.txt.gz”)的内容获得所需的结果,1)
;不需要子进程。如果我正在处理的文件不是那么大,我会使用这种方法。unix实用程序很高兴知道这一点;我最近使用了很多Python和gzip来处理大型XML文件。
import subprocess
import signal

print subprocess.check_output("zgrep -m1 'a' test.txt.gz", shell=True, , preexec_fn=lambda:signal.signal(signal.SIGPIPE, signal.SIG_DFL))