Awk 计算每列中的非空条目数,例如comm输出
Unix命令Awk 计算每列中的非空条目数,例如comm输出,awk,bash,unix,Awk,Bash,Unix,Unix命令comm file1 file2有一个3列输出,其中第一列为file1独有的行,第二列为file2独有的行,第三列为file1和file2共享的行(假设file1和file2已排序)。它最终看起来像这样: $ echo -e "alpha\nbravo\ncharlie" > file1 $ echo -e "alpha\nbravo\ndelta" > file2 $ comm file1 file2 alpha
comm file1 file2
有一个3列输出,其中第一列为file1独有的行,第二列为file2独有的行,第三列为file1和file2共享的行(假设file1和file2已排序)。它最终看起来像这样:
$ echo -e "alpha\nbravo\ncharlie" > file1
$ echo -e "alpha\nbravo\ndelta" > file2
$ comm file1 file2
alpha
bravo
charlie
delta
如果我想知道每列中非空行的数量,是否有一种通用的方法来解析comm
的输出并计算它们
我知道,特别是对于comm
,我可以直接运行
for i in {12,23,31}; do comm -$i file1 file2 | wc -l; done
但我对那些以
comm
输出文件为起点的解决方案很感兴趣,它们是为了更好地使用Unix命令行。我添加标签是因为我预感有一个好的awk解决方案。您可以使用此awk
:
comm file1 file2 |
awk -F '\t' -v OFS='\n' '{ if ($1=="") if ($2=="") c3++; else c2++; else c1++ }
END { print c3, c2, c1 }'
请注意,comm
的输出由以下大小写分隔:
- 公共行中的第一个和第二个空列
- 文件2特有的行中的第一个空列
- 文件1特有的行中的第一个非空列
awk
将工作做好的问题,但值得一提的是,GNU版本的comm有一个--total
选项,该选项将以类似的方式打印每列的总和。显然,您可以在awk
中完成所有操作,而无需comm
或需要排序输入
$ awk 'NR==FNR {a[$1]; next}
{if($1 in a) {c3++; delete a[$1]}
else c2++}
END {print length(a),c2,c3}' file1 file2
1 1 2
这仅适用于file1、file2和common
注意,这要求每个文件中的记录都是唯一的。这个问题很有趣,但并不像人们想象的那么简单,特别是如果您没有
--total
选项
关于通信的几件事:
comm
可用于已排序的文件file1
中出现n次,在file2
中出现m次ncomm
将在第2列中输出n-m个条目,在第3列中输出n个条目
$ comm <(echo -e "1\n2\n3") <(echo "2\n2\n3\n4")
1
2
2
3
4
幸运的是,它有一个定义分隔符的选项(--output delimiter=STR
)comm
仅当后面有其他非空字段时才添加分隔符
$ comm --output-delimiter=SEP <(echo -e "1\n2\n3") <(echo "2\n3\n4")
1 << NO SEP (1 field)
SEPSEP2 << TWO SEP (3 fields)
SEPSEP3 << TWO SEP (3 fields)
SEP4 << ONE SEP (2 fields)
该输出现在可以通过管道传输到极其简单的awk
$ awk -F "\001" '{a[NF]++}END{print a[1],a[2],a[3] }'
由于第(4)点,上述方法有效
所以你可以做:
$ comm --output-delimiter=$'\001' file1 file2 \
| awk -F "\001" '{a[NF]++}END{print a[1],a[2],a[3] }'
但是我没有--输出分隔符选项:
这需要纯awk
解决方案。我们跟踪3个阵列<对于file1
b
对于file2
和c
组合,代码>a。(c
跟踪所有条目)。我们一定要把第(2)点考虑进去
$ awk '(NR==FNR) { a[$0]++; c[$0]++ }
(NR!=FNR) { b[$0]++; c[$0]-- }
END { for(i in c) {
if (c[i] < 0) { countb+=-c[i]; countc+=a[i] }
else if (c[i] == 0) { countc+=a[i] }
else { counta+= c[i]; countc+=b[i] }
}
print counta, countb, countc
}' file1 file2
使用Perl
$ comm file1 file2 | perl -lne ' /^\t\t/ and $kv{2}++; /^\t\S+/ and $kv{1}++; /^\S+/ and $kv{3}++; END { print "col-$_:$kv{$_}" for(keys %kv) } '
col-3:1
col-1:1
col-2:2
$
或
在哪里
col-1->第一个文件
col-3->第二个文件
col-2->两个文件
,如果任何输出行包含制表符(由于输入行包含制表符),则将失败。如果任何输入文件以制表符分隔或其中包含制表符,则仍将失败。@kvantour;不,只要行不以TabThreak@EdMorton开头,情况就不是这样。TabThreak@EdMorton更短,更容易阅读(编辑)。您的逻辑是正确的,但正如您所指出的,如果一行以TabThreak开头,则会失败。(+1表示逻辑的惊人性)您应该在示例输入中包含制表符,因为这对可能的解决方案有很大影响(我实际上不认为一个健壮的解决方案是可能的,但我还没有考虑清楚)。还包括预期的输出。当然,如果您只对总计感兴趣,可以禁用通常的输出:comm-123--total file1 file2
。对于没有--total选项的用户,它是在8.26版中添加的:只有在文件中的行是唯一的情况下,此选项才能正常工作<代码>通信的操作方式不同。非常好地使用了--输出分隔符
,我想这在gnu
版本上是可用的
$ comm --output-delimiter=$'\001' file1 file2 \
| awk -F "\001" '{a[NF]++}END{print a[1],a[2],a[3] }'
$ awk '(NR==FNR) { a[$0]++; c[$0]++ }
(NR!=FNR) { b[$0]++; c[$0]-- }
END { for(i in c) {
if (c[i] < 0) { countb+=-c[i]; countc+=a[i] }
else if (c[i] == 0) { countc+=a[i] }
else { counta+= c[i]; countc+=b[i] }
}
print counta, countb, countc
}' file1 file2
$ awk '(NR==FNR) { a[$0]++; c[$0]++; next } { c[$0]-- }
END { for(i in c) {
counta+=(c[i]>0 ? c[i] : 0)
countb-=(c[i]<0 ? c[i] : 0)
countc+=a[i] - (c[i]>0 ? c[i] : 0)
}
print counta, countb, countc
}' file1 file2
$ comm file1 file2 | perl -lne ' /^\t\t/ and $kv{2}++; /^\t\S+/ and $kv{1}++; /^\S+/ and $kv{3}++; END { print "col-$_:$kv{$_}" for(keys %kv) } '
col-3:1
col-1:1
col-2:2
$
$ comm file1 file2 | perl -lne ' /(^\t\t)|(^\t\S+)|(^.)/ and $x=$+[0]>2?3:$+[0]; $kv{$x}++; END { print "col-$_:$kv{$_}" for(keys %kv) } '
col-3:1
col-1:1
col-2:2
$