20.27 比较两个文本文件 https://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 1) 只存在于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) 只存在于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 3) 对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时要注意,具体问题具体处理吧。 4) 对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不是用于此处的,必须借助于其他工具。 5) 只存在于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 1) $ time /usr/xpg4/bin/grep -vxFf 1.txt 2.txt > /dev/null real 6m17.880s user 4m36.809s sys 0m0.142s 2) 尽管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 3) $ time comm -13 1.txt 2.txt > /dev/null real 0m0.191s user 0m0.155s sys 0m0.011s 4) $ 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 1) 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 2) 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) 假设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排序输出了,出乎意料。