限制查找输出并避免信号 13

限制查找输出并避免信号 13

我有一个包含约 1M 个文件的目录,需要搜索特定模式。我知道如何对所有文件执行此操作:

find /path/ -exec grep -H -m 1 'pattern' \{\} \;

不需要完整的输出(太慢)。几次第一次点击都可以,所以我尝试限制行数:

find /path/ -exec grep -H -m 1 'pattern' \{\} \; | head -n 5

这会产生 5 行,然后是

find: `grep' terminated by signal 13

find继续工作。这很好解释这里。我尝试了quit行动:

find /path/ -exec grep -H -m 1 'pattern' \{\} \; -quit

这仅输出第一个匹配项。

是否可以将 find 输出限制为特定数量的结果(例如提供一个quit类似于 simple to 的参数head -n)?

答案1

由于您已经在使用 GNU 扩展(-quit, -H, -m1),您不妨使用 GNUgrep-r选项,因此--line-buffered它会在找到匹配项后立即输出匹配项,因此一旦找到匹配项,它更有可能被 SIGPIPE 杀死。它写了第六行:

grep -rHm1 --line-buffered pattern /path | head -n 5

对于find,您可能需要执行以下操作:

find /path -type f -exec sh -c '
  grep -Hm1 --line-buffered pattern "$@"
  [ "$(kill -l "$?")" = PIPE ] && kill -s PIPE "$PPID"
  ' sh {} + | head -n 5

也就是说,换行grepsh您仍然希望运行grep尽可能少的调用,因此是{} +),并在因 SIGPIPE 死亡时sh杀死其父级 ( find) 。grep

另一种方法可能是使用xargs作为-exec {} +.xargs当它生成的命令因信号而死亡时立即退出,因此:

 find . -type f -print0 |
   xargs -r0 grep -Hm1 --line-buffered pattern |
   head -n 5

-r并且-0是 GNU 扩展)。一旦grep写入损坏的管道,grepxargs都会退出,find并且下次打印内容时也会退出本身。跑find下去stdbuf -oL可能会让这件事发生得更快。

POSIX 版本可能是:

trap - PIPE # restore default SIGPIPE handler in case it was disabled
RE=pattern find /path -type f -exec sh -c '
  for file do
    awk '\''
      $0 ~ ENVIRON["RE"] {
        print FILENAME ": " $0
        exit
      }'\'' < "$file"
    if [ "$(kill -l "$?")" = PIPE ]; then
      kill -s PIPE "$PPID"
      exit
    fi
  done' sh {} + | head -n 5

效率非常低,因为它为每个文件运行多个命令。

答案2

避免错误的解决方案可能是这样的:

find / -type f -print0 \
  | xargs -0 -L 1 grep -H -m 1 --line-buffered 2>/dev/null \
  | head -10

在此示例中,一旦命令失败,xargs 将停止,因此只会出现一个管道错误,该错误将被 stderr 重定向过滤。

答案3

grep一次一个文件。使用您的-quit,您可以在第一个成功的 grep 处停止查找。

[更新] 我的第一个解决方案是一次 grep 多个文件:

find /path/ -type f -exec grep -H -m 1 'pattern' \{\} + -quit | head -n 5

(神奇之处在于子命令+末尾的。添加了 。如果您确定 /path/ 包含多个文件,您可能需要删除该选项)-exec-type f-Hgrep

正如 @StéphaneChazelas 所报告的,这里的问题是该-exec命令是异步执行的,并且总是返回true=>find在第一个文件处退出。

如果我们想在完成find时停止,还必须接收到正在接收的SIGPIPE (信号13)。这意味着必须通过管道发送一些东西。headfindgrepfind

这是一个快速而肮脏的技巧,并根据 Stéphane 的建议进行了增强:

find /path/ -type f -exec grep -H -m 1 --line-buffered 'pattern' {} + -printf '\r' | head -n 5

随着-printf '\r'我强制find输出一个无害的字符,它(希望)不会改变 的输出grep。一旦head停止,find将收到 SIGPIPE 并停止。

[update2] 我警告过你这是一个肮脏的黑客行为。这是一个更好的解决方案:

find /path/ -type f -exec grep --quiet 'pattern' {} ";" -print | head -n 5

在这里,这不再是grep打印文件名,而是find=> 不再有“grep 由信号 13 终止”并findhead.问题是 不再打印匹配的行grep

[update3] 最后,按照@Andrey的建议,下面的无耻丑陋命令将解决最后一个问题:

find /path/ -type f \
    -exec grep --quiet 'pattern' {} \; \
    -printf '%p:' \
    -exec grep -h -m 1 'pattern' {} \; \
| head -n 5`

答案4

对于更简单的情况,替代路线可能只是一个此处字符串而不是管道。例如-

find . -exec stat -c %y {} \; | head -n1

会看到与上面相同的问题。一种可以考虑的简单方法 -

head -n1 <<<$(find . -exec stat -c %y {} \;)

相关内容