这个问题很有趣,但并不像人们想象的那么简单,尤其是在您没有--total 选项的情况下。
关于comm的几点说明:
-
comm 适用于排序的文件
-
如果一行在file1 中出现n 次,在file2 中出现m 次n 次,comm 将在第 2 列输出 n-m 个条目,在第 3 列输出 n 个条目。
$ comm <(echo -e "1\n2\n3") <(echo "2\n2\n3\n4")
1
2
2
3
4
-
comm 使用 -character 作为默认分隔符,如果您的输入包含此字符,则处理其输出会出现问题。
$ comm <(echo -e "1\t2\n3") <(echo "2\n3\n4")
1 2 << this is the weird line
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)
我们现在该如何解决:
我们显然不应该使用 ASCII 符号作为分隔符,这在处理 ASCII 文件时会出现问题,所以您可以做的是使用不可打印的字符作为分隔符。例如,您可以使用带有八进制值 \001 的 字符(它不接受 字符)。这通常可以解决您可能因第 (3) 点而遇到的问题
$ comm --output-delimiter=$'\001' <(echo -e "1\t2\n3") <(echo "2\n3\n4")
这个输出现在可以通过管道传输到一个非常简单的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] }'
但我没有 --output-delimiter 选项: 这需要纯 awk 解决方案。我们跟踪 3 个数组。 a 用于 file1 b 用于 file2 和 c 用于组合。 (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
我们基本上可以去掉数组b,因为它可以派生自a 和c,但我想让它更清楚一点,它是如何工作的;另一个版本是:
$ 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