![计算某个字符串在多列中出现 n 次的行数](https://linux22.com/image/12299/%E8%AE%A1%E7%AE%97%E6%9F%90%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%9C%A8%E5%A4%9A%E5%88%97%E4%B8%AD%E5%87%BA%E7%8E%B0%20n%20%E6%AC%A1%E7%9A%84%E8%A1%8C%E6%95%B0.png)
我有数百个文本文件,每个文件由五个制表符分隔的列组成。第一列包含一个索引,后面四列包含出现次数。现在我想计算包含 3 列且值为 0 的行数(即下面示例中的 7 行)。
1 0 0 0 9
2 0 9 0 0
3 10 0 0 0
4 0 10 4 0
5 0 0 0 10
6 0 0 0 10
7 0 0 0 10
8 0 10 0 0
9 5 0 5 0
我可以将其编码为 R 中的循环,但由于原始文件每个包含 60 多万行,我想知道 awk 或 sed 和 wc -l 是否没有解决方法。
答案1
是的,您可以在以下位置执行此操作awk
:
awk '{
k=0;
for(i=2;i<=NF;i++){
if($i == 0){
k++
}
}
if(k==3){
tot++
}
}
END{
print tot
}' file
还有 (GNU)sed
和wc
:
$ sed -nE '/\b0\b.*\b0\b.*\b0\b/p' file | wc -l
7
但是,就我个人而言,我会用 perl 代替:
$ perl -ale '$tot++ if (grep{$_ == 0 } @F) == 3 }{ print $tot' file
7
或者,稍微不那么浓缩:
$ perl -ale 'if( (grep{$_ == 0 } @F) == 3 ){
$tot++
}
END{
print $tot
}' file
7
对于你们当中的高尔夫球手来说也是如此:
$ perl -ale '(grep{$_==0}@F)==3&&$t++}{print$t' file
7
解释
-ale
:-a
使 Perl 表现得像 awk。它将读取输入文件的每一行并将其按空格分割到数组中@F
。-l
向\n
每次调用添加print
并从输入中删除尾随换行符,并且 是-e
应应用于每行输入的脚本。$tot++ if (grep{$_ == 0 } @F) == 3
:$tot
每次恰好有 3 个字段为 时,递增 10
。由于第一个字段从 1 开始,我们知道它永远不会是 0,因此我们不需要排除它。}{
:这只是一种简写方式END{}
,给出将在文件处理后执行的代码块。因此,}{ print $tot
将打印包含三个值为 的字段的总行数0
。
答案2
与GNU grep
或ripgrep
$ LC_ALL=C grep -c $'\t''0\b.*\b0\b.*\b0\b' ip.txt
7
$ rg -c '\t0\b.*\b0\b.*\b0\b' ip.txt
7
where$'\t'
将匹配制表符,因此即使第一列是 也可以工作0
。
使用大文件运行示例:
$ perl -0777 -ne 'print $_ x 1000000' ip.txt > f1
$ du -h f1
92M f1
$ time LC_ALL=C grep -c $'\t''0\b.*\b0\b.*\b0\b' f1 > f2
real 0m0.416s
$ time rg -c '\t0\b.*\b0\b.*\b0\b' f1 > f3
real 0m1.271s
$ time LC_ALL=C awk 'gsub(/\t0/,"")==3{c++} END{print c+0}' f1 > f4
real 0m8.645s
$ time perl -ale '$tot++ if (grep{$_ == 0 } @F) == 3 }{ print $tot' f1 > f5
real 0m14.349s
$ time LC_ALL=C sed -n 's/\t0\>//4;t;s//&/3p' f1 | wc -l > f6
real 0m14.075s
$ time LC_ALL=C sed -n 's/\t0\>/&/3p' f1 | wc -l > f8
real 0m6.772s
$ time LC_ALL=C awk '{
k=0;
for(i=2;i<=NF;i++){
if($i == 0){
k++
}
}
if(k==3){
tot++
}
}
END{
print tot
}' f1 > f7
real 0m10.675s
LC_ALL=C
如果文件可以包含非 ASCII 字符,则删除。ripgrep
通常比GNU grep
但在测试运行中GNU grep
更快。根据ripgrep
作者的说法,(?-u:\b)
可以用来避免 unicode 字边界,但这导致上述情况的时间相似。
答案3
使用 GNU sed:
sed -E 's/\t0\>/&/3;t;d' file | wc -l
正如 Isaac 所指出的,如果我们想精确计算 3,那么可以这样做:
sed -n 's/\t0\>//4;t;s//&/3p' file | wc -l
答案4
使用 Perl 计算左侧被 TAB 包围、右侧被单词边界包围的 0 的行数,共 3 次。最后打印这些行的行数。
perl -lne '$c += 3 == (() = /\t0\b/g)}{print $c' file
7
另一种方法是查看字段:
perl -F'\t' -lane '$c++ if 3 == grep ! $_, @F[1..$#F]}{print $c' file
s///
另一种方法是在标量上下文中使用该命令:
perl -lne '$c += s/\t0\b//g == 3}{print $c' file
我们使用 Gnu awk 来执行此操作:
awk -F'\t' '
{
gsub(FS, FS FS)
$0 = $0 FS
if ($0 != gensub(FS"0"FS, "", 3, $0)) ++c
}
END{print c}
' file
Gnu grep 还可以帮助您:
grep -cP '(.*\t0\b.*){3}' file