【问题标题】:grep - how to output progress bar or statusgrep - 如何输出进度条或状态
【发布时间】:2016-06-07 15:40:52
【问题描述】:

有时我在grep-ing 数千个文件,很高兴看到某种进展(栏或状态)。

我知道这不是微不足道的,因为grep 将搜索结果输出到 STDOUT,而我的默认工作流程是将结果输出到文件并希望输出进度条/状态STDOUTSTDERR

是否需要修改grep的源代码?

理想的命令是:

grep -e "STRING" --results="FILE.txt"

以及进度:

[curr file being searched], number x/total number of files

写入 STDOUTSTDERR

【问题讨论】:

  • 你考虑过用脚本来做吗?比编辑grep源码简单

标签: bash grep


【解决方案1】:

这不一定需要修改grep,尽管通过这样的修改您可能会获得更准确的进度条。

如果您通过一次 grep 调用来搜索“数千个文件”,则很可能您正在使用 -r 选项来递归目录结构。在这种情况下,甚至不清楚grep 知道它将检查多少个文件,因为我相信它会在探索整个目录结构之前开始检查文件。首先探索目录结构可能会增加总扫描时间(事实上,生成进度报告总是有成本的,这就是为什么很少有传统的 Unix 实用程序这样做的原因。)

在任何情况下,通过构建要扫描的文件的完整列表,然后将它们分批提供给grep,可能会获得一个简单但稍微不准确的进度条,可能是 100 个,也可能基于总数批次的大小。小批量将允许更准确的进度报告,但它们也会增加开销,因为它们需要额外的 grep 进程启动,并且进程启动时间可能比 grep 小文件更多。进度报告将针对每批文件进行更新,因此您需要选择一个能够定期更新且不会过多增加开销的批量大小。基于文件总大小的批处理大小(例如,使用stat 来获取文件大小)会使进度报告更准确,但会增加进程启动的额外成本。

此策略的一个优点是您还可以并行运行两个或多个 grep,这可能会加快处理速度。


从广义上讲,一个简单的脚本(它只是按数量划分文件,而不是按大小划分,并且不尝试并行化)。

# Requires bash 4 and Gnu grep
shopt -s globstar
files=(**)
total=${#files[@]}
for ((i=0; i<total; i+=100)); do
  echo $i/$total >>/dev/stderr
  grep -d skip -e "$pattern" "${files[@]:i:100}" >>results.txt
done

为简单起见,我使用 globstar (**) 将所有文件安全地放入一个数组中。如果你的 bash 版本太旧,那么你可以通过循环 find 的输出来完成,但如果你有很多文件,这不是很有效。不幸的是,我不知道如何编写只匹配文件的 globstar 表达式。 (**/ 只匹配目录。)幸运的是,GNU grep 提供了-d skip 选项,它可以静默地跳过目录。这意味着文件计数会稍微不准确,因为会计算目录,但它可能没有太大区别。

您可能希望通过使用一些控制台代码使进度报告更清晰。以上只是为了让您入门。

将其划分为不同进程的最简单方法是将列表划分为 X 个不同的段并运行 X 个不同的 for 循环,每个循环都有不同的起点。但是,它们可能不会同时完成,因此这是次优的。更好的解决方案是 GNU 并行。你可以这样做:

find . -type f -print0 |
parallel --progress -L 100 -m -j 4 grep -e "$pattern" > results.txt

(这里-L 100 指定最多应为每个 grep 实例提供 100 个文件,-j 4 指定四个并行进程。我刚刚从空中提取了这些数字;您可能想要调整它们。 )

【讨论】:

  • 很好且几乎完整的答案。请发布一个关于如何使用find, parallel, grep 命令完成任务的示例,我将其标记为已接受。
  • @adrian:了解您当前如何调用 grep 会有所帮助:-r 只是猜测。
  • 我常用的 grep 命令是grep -e "STRING" * -r。一次处理一批 *X 个文件是一个完美的主意。
  • @Adrian:好的,添加了一些具体的例子,但你可能仍然想摆弄它们。祝你好运。
【解决方案2】:

试试并行程序

find * -name \*.[ch] | parallel -j5 --bar  '(grep grep-string {})' > output-file

虽然我发现这比简单的要慢

find * -name \*.[ch] | xargs grep grep-string > output-file

【讨论】:

    【解决方案3】:

    此命令显示进度(速度和偏移量),但不显示总量。不过,这可以手动估算。

    dd if=/input/file bs=1c skip=<offset> | pv | grep -aob "<string>"
    

    【讨论】:

      【解决方案4】:

      我很确定您需要更改 grep 源代码。这些变化将是巨大的。

      目前grep 在完成对整个文件的解析之前不知道一个文件有多少行。根据您的要求,它需要解析文件 2 次,或者至少以其他方式确定完整的行数。

      它将第一次确定进度条的行数。第二次它实际上会搜索您的模式。

      这不仅会增加运行时间,还会违反主要的 UNIX 哲学之一。

      1. 让每个程序做好一件事。要完成一项新工作,请重新构建而不是通过添加新的“功能”使旧程序复杂化。 (source)

      可能还有其他工具可以满足您的需要,但 afaik grep 不适合这里。

      【讨论】:

      • OP 没有说任何关于行数的内容,只说文件。甚至不清楚行数是否有用。要收集的更简单的统计数据是总字节数(您可以从调用统计数据中获得),这也是一个更准确的统计数据,因为 grep 实际上读取的是块,而不是行。但是,我同意您回答的基本理念。
      • 对不起,我误解了输出“数字 x”,以为他的意思是文件 y 中的第 x 行。
      【解决方案5】:

      我通常使用这样的东西:

      grep | tee "FILE.txt" | cat -n | sed 's/^/match: /;s/$/     /' | tr '\n' '\r' 1>&2
      

      它并不完美,因为它只显示匹配项,如果它们的长度过长或长度相差很大,则会出现错误,但它应该为您提供总体思路。

      或者一个简单的点:

      grep | tee "FILE.txt" | sed 's/.*//' | tr '\n' '.' 1>&2
      

      【讨论】:

      • 这如何表示状态?
      • grep -e "STRING" | tee "FILE.txt" 希望是您的grep -e "STRING" --results="FILE.txt" 的答案,但它并不意味着像x/total number of files 这样的完整状态。它只显示已处理的匹配数。
      猜你喜欢
      • 1970-01-01
      • 2023-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-03
      • 2022-11-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多