如何输出进度条或状态



有时我正在grep处理数千个文件,如果能看到一些进展(栏或状态),那就太好了。

我知道这不是一件小事,因为grep将搜索结果输出到STDOUT,而我的默认工作流程是将结果输出到一个文件,并希望将进度条/状态输出到
STDOUT或
STDERR。

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

理想的命令是:

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

和进展:

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

写入STDOUTSTDERR

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

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

在任何情况下,都可以通过构建要扫描的文件的完整列表,然后将它们以一定大小(可能是100)或基于批次的总大小的批次提供给grep,来获得简单但稍微不准确的进度条。小批量可以提供更准确的进度报告,但也会增加开销,因为它们需要额外的grep进程启动,并且进程启动时间可能比grepping一个小文件更长。进度报告将针对每批文件进行更新,因此您希望选择一个批量大小,以便在不增加太多开销的情况下进行定期更新。将批大小基于文件的总大小(例如,使用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个不同循环,每个循环都有不同的起点。然而,它们可能不会同时完成,所以这是次优的。一个更好的解决方案是GNU并行。你可以这样做:

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

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

尝试并行程序

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

尽管我发现这比简单的慢

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

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

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

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

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

第一次它将确定进度条的行数。第二次它会真正地搜索你的模式。

这不仅会增加运行时间,而且违反了UNIX的主要理念之一。

  1. 让每个程序都做好一件事。要做一项新的工作,就要重新构建,而不是通过添加新的"功能"来使旧程序复杂化。(来源)

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

我通常使用这样的东西:

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

相关内容

  • 没有找到相关文章

最新更新