Skip to content

Latest commit

 

History

History
393 lines (281 loc) · 6.41 KB

201509101800.txt.md

File metadata and controls

393 lines (281 loc) · 6.41 KB

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 $1;}' $ comm -3 <(sort 1.txt) <(sort 2.txt) | awk '{print $1;}' 0 1 7 9 b c

如果某行内容有空格,使用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 $2;}' $ diff 1_.txt 2_.txt | grep "^>" | awk '{print substr($0,3);}' c

只存在于1_.txt中的行:

$ diff 1_.txt 2_.txt | grep "^<" | awk '{print $2;}' $ diff 1_.txt 2_.txt | grep "^<" | awk '{print substr($0,3);}' 0 1 7 9 b

两个文件的交集:

$ 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(!($0 in xxx))print}' 1.txt 2.txt $ nawk 'NR==FNR{xxx[$0];next}!($0 in xxx)' 1.txt 2.txt c

如果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(!($0 in xxx))print}' 1.txt 2.txt $ awk 'ARGIND==1{xxx[$0]}ARGIND>1&&!($0 in xxx){print}' 1.txt 2.txt $ awk 'NR==FNR{xxx[$0]}NR>FNR&&!($0 in xxx){print}' 1.txt 2.txt c

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(!($0 in xxx))print}' 2.txt 1.txt $ nawk 'NR==FNR{xxx[$0];next}!($0 in xxx)' 2.txt 1.txt 1 7 9 0 b

两个文件的交集:

$ 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排序输出了,出乎意料。