如何有效地列出具有'n`行的文件?

时间:2016-09-11 00:41:48

标签: bash performance file awk find

为了列出具有完全n行的文件,可以执行

n=5
find . -name "*.txt" | xargs wc -l | awk -v n=${n} -F" " '{if ($1==n) {print $2} }'

但是这个解决方案非常慢,因为它先计算每个文件的行数,然后只选择那些有n行的行。当行到达n+1行时计算行和停止的过程会更有效(特别是在处理有大量行的大文件时)。

如何有效地列出具有n行的文件?

注意,对于特殊情况,每行的大小完全相同,那么可能会做

n=5
sizePerLine=500
find . -name '*.txt' -size $(( ${n} * ${sizePerLine} ))

9 个答案:

答案 0 :(得分:3)

我认为以下内容会更快:

find . -name "*.txt" -exec awk -v n="$n" 'FILENAME != prevfile {if(prevfnr==n) print prevfile} {prevfile = FILENAME; prevfnr = FNR; if(FNR>n) {nextfile;}} END{if (FNR==n) {print FILENAME} }' {} +

工作原理:

  • 使用-exec ... {} +使用find为每个文件执行命令,并让它在每次调用时传递多个args
  • awk -v n="$n"调用awk并定义名为awk的{​​{1}}变量,使其与shell变量n
  • 具有相同的值
  • n检查当前文件是否与最后一条记录相同,如果没有,则查看前一个文件是否有FILENAME != prevfile {if(prevfnr==n) print prevfile条记录,如果是,则打印该文件的名称
  • n使用当前{prevfile = FILENAME; prevfnr = FNR; if(FNR>n) {nextfile;}}更新prevfile变量,使用当前FILENAME更新prevfnr变量。此外,如果我们当前的文件记录超过FNR,则跳转到下一个文件而不再处理任何其他文件
  • 最后
  • n查看最后一个文件是否也包含END{if (FNR==n) {print FILENAME}条记录

有趣的是,我发现这实际上给出了与使用n的版本不同的结果,尽管我认为这个实际上可能更正确。对于我的目录中最后一行不包含行结束字符wc -l的文件,将报告行数,不计算最后一行“未终止”行,但此处的解决方案将计算它。

Arg,我没有意识到wc -l是一个GNU主义。如果我已经限制自己,我们就可以把它变得更加清洁

nextfile

在我看来,POSIX find . -name '*.txt' -exec awk -v n="$n" 'FNR > n {nextfile;} ENDFILE{if (FNR==n) {print FILENAME} }' {} + 有一个很好的快捷方式来跳转到下一个文件,这是该解决方案需要的关键效率

答案 1 :(得分:3)

find . -name '*.txt' -print0 |
xargs -0 -n 1 awk -v n="$n" 'NR>n{exit} END{if (NR==n) print FILENAME}'

甚至更有效地使用GNU awk for ENDFILE:

find . -name '*.txt' -exec \
awk -v n="$n" 'FNR>n{nextfile} ENDFILE{if (FNR==n) print FILENAME}' {} +

上面脚本的主要效率是在你点击大于n的行号时退出awk工作循环(即跳转到END / ENDFILE部分),而不是等到整个文件已经存在在检查读取的行数之前阅读。

在gawk脚本中显示nextfileexit之间的区别:

$ seq 10 | awk '{print; nextfile} ENDFILE{print "x"} END{print "y"}'
1
x
y

$ seq 10 | awk '{print; exit} ENDFILE{print "x"} END{print "y"}'
1
y

答案 2 :(得分:1)

使用grep

n=5
find . -name '*.txt' | xargs grep '.+' -m $((n+1)) -c | grep ':'$n'$'

这告诉grep仅检查第一行n+1行,并仅显示包含n行的文件。

替换xargs ag '\n' -m$n -c如果你有一个很好的加速 - ag是一个比grep更快的搜索者。请注意-m仅适用于GNU grep;在BSD grep上,它是一个全局选项(改为使用ag,或者获取GNU grep)。

答案 3 :(得分:1)

使用awk本身:

n=5
find . -name '*.txt' | xargs -n 1 awk -e "{ n++; if (n > $n) {exit 1} } END { if ( n == $n ) print FILENAME}"

如果文件有+5行,这将退出,否则将打印精确的5行。

答案 4 :(得分:1)

您可能会过度复杂化,只需使用for循环和test条件进行评估,例如

for f in *.txt; do [ $(wc -l <"$f") -eq "5" ] && echo "$f"; done

这将在当前目录中找到包含.txt行的所有5个文件。

答案 5 :(得分:1)

使用perl

n=5 find /some/dir -type f -name '*.txt' -exec \
    perl -lnE '{ $. == $ENV{n} and eof and say $ARGV } continue { close ARGV if($. == $ENV{n} or eof) }' {} +

答案 6 :(得分:1)

使用grepawk

$ grep -cr "^" *|awk -F: '$2==6 {print $1}'

故障:

  • grep -c计算文件
  • 中匹配行的数量
  • -r是--recursive
  • "^"匹配(即计数)行的开头

grep的输出是:

foo:6
dir/bar:7
  • awk使用:作为字段分隔符和打印文件名(以及相关路径)的文件,其中行数是给定的。

答案 7 :(得分:1)

更有效的解决方案是将findgawk一起使用条件ENDFILEFNR

find . -name '*.txt' -exec awk -v n=$n 'ENDFILE{if(FNR==n) print FILENAME}' {} +

对于任何需要将wc -l的整个输出传递给另一个解析其输出的进程的解决方案,这会将时间减少一半。也就是说,使用ENDFILEnextfile的其他答案甚至更有效,因为它们允许在达到所需行数时跳到下一个文件。

假设您正在使用Bash&gt; 4.0,可以删除find的需要,以利用允许递归扩展文件名的globstar bash选项。只要参数的数量不超过gawk的{​​{1}}限制,这应该有效。

ARGC

答案 8 :(得分:1)

使用Bash≥4,这是检查文本文件是否有5行的一种相当有效的方法:

mapfile -n 6 -t lines < file
if (( ${#lines[@]} == 5 )); then
    echo "has 5 lines"
else
    echo "doesn't have 5 lines"
fi

我们将mapfile-n 6一起使用,以便不超过6行(效率)。

find命令一起,我们获得:

find . -name '*.txt' -type f -exec bash -c 'mapfile -n 6 -t lines < "$1"; ((${#lines[@]}==5))' _ {} \; -print

你也可以在bash语句中使用-exec ... +和一个循环(练习留给读者)。