【问题标题】:Error after zgrep system call from python for large files only仅针对大文件从 python 调用 zgrep 系统后出错
【发布时间】:2012-06-07 20:57:33
【问题描述】:

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

脚本:

#! /usr/bin/env python2.7

import subprocess

print subprocess.check_output("zgrep -m1 'a' test.txt.gz", shell=True)

错误:

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

> ./broken-zgrep.py

gzip: stdout: Broken pipe
Traceback (most recent call last):
  File "./broken-zgrep.py", line 25, in <module>
    print subprocess.check_output("zgrep -m1 'a' test.txt.gz", shell=True)
  File "/usr/intel/pkgs/python/2.7/lib/python2.7/subprocess.py", line 537, in check_output
    raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command 'zgrep -m1 'a' test.txt.gz' returned non-zero exit status 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/urandom | hexdump | head -n 100000 | gzip > test.txt.gz

看似无关的更改可以防止错误:

  • 制作一个更小的测试文件

    cat /dev/urandom | hexdump | head -n 100 | gzip &gt; test.txt.gz

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

    print subprocess.check_output("zgrep 'a' test.txt.gz", shell=True)

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

    cat /dev/urandom | hexdump | head -n 100000 &gt; test.txt

    print subprocess.check_output("grep -m1 'a' test.txt", shell=True)

  • perl中运行等效命令

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


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

我对原因的最佳猜测是阅读有关-m 选项的grep man 页面。

   -m NUM, --max-count=NUM
          Stop reading a file after NUM matching lines.  If the  input  is
          standard  input  from a regular file, and NUM matching lines are
          output, grep ensures that the standard input  is  positioned  to
          just  after the last matching line before exiting, regardless of
          the presence of trailing context lines.  This enables a  calling
          process  to resume a search.  When grep stops after NUM matching
          lines, it outputs any trailing context lines.

我最初假设-m 选项只会导致grep 在找到NUM 个匹配项后退出。但是,grep 和标准输入可能会发生一些有趣的事情。这仍然不能解释为什么错误只发生在大型压缩文件中。

我最终将我的脚本从 python 移植到 perl 来解决这个问题,因此没有任何迫切需要解决方案。但我真的很想更好地理解为什么这场完美的环境风暴会失败。

【问题讨论】:

  • 请注意,您可以通过itertools.islice((ln for ln in gzip.open("test.txt.gz") if re.search("a", ln)), 1) 之类的方式获得所需的结果;不需要子流程。
  • 如果我正在处理的文件不是那么大,我会使用这种方法。 unix 实用程序很多 faster
  • 很高兴知道这一点;我最近一直在使用大量 Python 和 gzip 来处理大型 XML 文件。

标签: python grep subprocess


【解决方案1】:

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

如果此时 gunzip 还没有完成文件的解压,未来对 gunzip 的标准输出(连接到 grep 的标准输入)的写入将失败。这正是您的情况:

gzip: stdout: Broken pipe

【讨论】:

  • 那么与 Perl 有什么区别呢? Perl 是否抑制错误消息?
  • 嗯,我不知道为什么。通常,stdout: Broken pipe 错误首先不应该发生。涉及很多缓冲,这可能会使事情变得奇怪(即更大的文件和更快/更慢的磁盘的不同行为)。
  • 啊,这就解释了为什么它只发生在大文件中。我仍然很好奇python是否有办法忽略生成的错误以及为什么在shell中手动运行命令会产生退出代码0。
【解决方案2】:

感谢 MilesF,这篇文章完美地解释了它: https://blog.nelhage.com/2010/02/a-very-subtle-bug/

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))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-03
    • 2020-08-12
    相关资源
    最近更新 更多