20.27 比较两个文本文件
http://scz.617.cn/unix/201509101800.txt
Q:
在Solaris 10上有两个文本文件,分别是1.txt、2.txt。现在想求只存在于2.txt中 的行。
$ cat 1.txt a 1 2 3 4 5 7 9 0 b
$ cat 2.txt 5 2 3 4 a c
A: scz
只存在于2.txt中的行:
$ /usr/xpg4/bin/grep -vxFf 1.txt 2.txt c
只存在于1.txt中的行:
$ /usr/xpg4/bin/grep -vxFf 2.txt 1.txt 1 7 9 0 b
两个文件的交集:
$ /usr/xpg4/bin/grep -xFf 1.txt 2.txt 5 2 3 4 a
两个文件的对称差:
$ /usr/xpg4/bin/grep -hvxFf <(/usr/xpg4/bin/grep -xFf 1.txt 2.txt) 1.txt 2.txt 1 7 9 0 b c
这里不能用/usr/bin/grep,它不支持-xFf这三种参数。
只存在于2.txt中的行:
$ sort 1.txt 1.txt 2.txt | uniq -u c
只存在于1.txt中的行:
$ sort 2.txt 2.txt 1.txt | uniq -u 0 1 7 9 b
两个文件的交集:
$ sort 1.txt 2.txt | uniq -d 2 3 4 5 a
两个文件的对称差:
$ sort 1.txt 2.txt | uniq -u 0 1 7 9 b c
对1.txt、2.txt进行排序后,可以用comm命令,该命令只适合处理排序过的文本文件。
sort 1.txt > 1_.txt sort 2.txt > 2_.txt
只存在于2_.txt中的行:
$ comm -13 1_.txt 2_.txt $ comm -13 <(sort 1.txt) <(sort 2.txt) c
如果1.txt、2.txt是几万行的大文件,务必先排序,后用comm,而不是用grep -vxf 求只存在于2.txt中的行。实测过,前者的效率秒杀后者。"排序+comm"耗时远小于 grep耗时,后者耗时超乎想像的长。
只存在于1_.txt中的行:
$ comm -23 1_.txt 2_.txt $ comm -23 <(sort 1.txt) <(sort 2.txt) 0 1 7 9 b
两个文件的交集:
$ comm -12 1_.txt 2_.txt $ comm -12 <(sort 1.txt) <(sort 2.txt) 2 3 4 5 a
两个文件的对称差:
$ comm -3 1_.txt 2_.txt | awk '{print
如果某行内容有空格,使用awk时要注意,具体问题具体处理吧。
对1.txt、2.txt进行排序后,可以用diff命令。
sort 1.txt > 1_.txt sort 2.txt > 2_.txt
只存在于2_.txt中的行:
$ diff 1_.txt 2_.txt | grep "^>" | awk '{print
只存在于1_.txt中的行:
$ diff 1_.txt 2_.txt | grep "^<" | awk '{print
两个文件的交集:
$ diff -u 1_.txt 2_.txt | grep "^ " | sed -e "s/^[ ]//" 2 3 4 5 a
两个文件的对称差:
$ diff -u 1_.txt 2_.txt | egrep -v "^( |@@|--|++)" | sed -e "s/^[-+]//" 0 1 7 9 b c
显然diff不是用于此处的,必须借助于其他工具。
只存在于2.txt中的行:
$ nawk 'NR==FNR{xxx[$0]=1;next}{if(xxx[$0]!=1)print}' 1.txt 2.txt
$ nawk 'NR==FNR{xxx[$0];next}{if(!(
如果awk处理多于1个的文件时,NR不等价于FNR,后者是每个文件相关的。
next的意思是"skip remaining patterns on this input line"。
Linux的gawk支持ARGIND,其对应待处理文件序号(从1计)。因此有其他写法:
$ awk 'ARGIND==1{xxx[$0]=1;next}{if(xxx[$0]!=1)print}' 1.txt 2.txt
$ awk 'ARGIND==1{xxx[$0];next}{if(!(
awk的数组元素实际是(key,value)形式,可以用"for ( key in array )"遍历数组。
只存在于1.txt中的行:
$ nawk 'NR==FNR{xxx[$0]=1;next}{if(xxx[$0]!=1)print}' 2.txt 1.txt
$ nawk 'NR==FNR{xxx[$0];next}{if(!(
两个文件的交集:
$ nawk 'NR==FNR{xxx[$0]=1}NR>FNR{if(xxx[$0]==1)print}' 1.txt 2.txt $ nawk 'NR==FNR{xxx[$0]=1;next}{if(xxx[$0]==1)print}' 1.txt 2.txt $ nawk 'NR==FNR{xxx[$0];next}$0 in xxx' 1.txt 2.txt 5 2 3 4 a
两个文件的对称差:
$ nawk 'NR==FNR{xxx[$0];next}$0 in xxx{delete xxx[$0];next}1;END{for(x in xxx)print x}' 1.txt 2.txt c 7 9 b 0 1
awk求对称差的方案实在太变态了。
D: scz 2015-09-25 09:46
下列1.txt、2.txt已经用"sort -u"排序、消重过。针对"求只存在于2.txt中的行"进 行一次性能测试。
$ ls -l 1.txt 2.txt -rw------- 1 root root 506595 Jul 24 19:01 1.txt -rw------- 1 root root 472830 Jul 24 19:01 2.txt $ wc -l 1.txt 2.txt 33773 1.txt 31522 2.txt 65295 total
$ time /usr/xpg4/bin/grep -vxFf 1.txt 2.txt > /dev/null real 6m17.880s user 4m36.809s sys 0m0.142s
尽管1.txt、2.txt已经"sort -u"处理过,但为了求只存在于2.txt中的行,只能再次 "sort+uniq"。
$ time sh -c "sort 1.txt 1.txt 2.txt | uniq -u > /dev/null" real 0m1.374s user 0m1.240s sys 0m0.058s
$ time comm -13 1.txt 2.txt > /dev/null real 0m0.191s user 0m0.155s sys 0m0.011s
$ time nawk 'NR==FNR {xxx[$0]=1;next}{if(xxx[$0]!=1) print}' 1.txt 2.txt > /dev/null
real 0m1.453s user 0m1.142s sys 0m0.036s
结论:
grep << sort+uniq、awk < comm
实测表明grep最慢,差出去数量级了。sort+uniq或awk比grep快很多。comm是最快的。 不过,comm占了预排序的便宜。如果考虑预排序本身耗时,再考虑兼容性、可移植性 等等,事实上sort+uniq或awk已经相当出色。
Q:
已知1.txt、2.txt内容如下:
$ cat 1.txt c 1 c_1 a 4 a_4 z 6 z_6 g 7 g_7
$ cat 2.txt 1 c 7 g 6 z
想得到这种结果:
c 1 c_1 g 7 g_7 z 6 z_6
A: scz 2016-01-22 09:36
nawk 'NR==FNR{xxx[$2]=$0;next}$1 in xxx{print xxx[$1]}' 1.txt 2.txt nawk 'NR==FNR{xxx[$2]=$0;next}{if($1 in xxx)print xxx[$1]}' 1.txt 2.txt nawk 'NR==FNR{xxx[$2]=$0;next}xxx[$1]{print xxx[$1]}' 1.txt 2.txt nawk 'NR==FNR{xxx[$2]=$0;next}{if(xxx[$1])print xxx[$1]}' 1.txt 2.txt
如果确认2.txt是1.txt的子集,可以简化成:
nawk 'NR==FNR{xxx[$2]=$0;next}{print xxx[$1]}' 1.txt 2.txt
输出:
c 1 c_1 g 7 g_7 z 6 z_6
join -1 2 -2 1 -o 1.1 1.2 1.3 1.txt 2.txt
输出:
c 1 c_1 g 7 g_7
因为join要求1.txt、2.txt排过序。
sort -k 2,2 1.txt > 1_.txt sort -k 1b,1 2.txt > 2_.txt join -1 2 -2 1 -o 1.1 1.2 1.3 1_.txt 2_.txt
输出:
c 1 c_1 z 6 z_6 g 7 g_7
不知为什么,下面这条命令会限入死循环:
join -1 2 -2 1 -o 1.1 1.2 1.3 <(sort -k 2,2 1.txt) <(sort -k 1b,1 2.txt)
假设3.txt只包含key:
$ cat 3.txt 1 7 6
还可以用:
/usr/xpg4/bin/grep -f 3.txt 1.txt /usr/xpg4/bin/grep -f <(cut -d' ' -f 1 2.txt) 1.txt
输出:
c 1 c_1 z 6 z_6 g 7 g_7
居然按key排序输出了,出乎意料。