终于明白awk如何牛掰地不排序也能去掉文件里的重复行了
因为工作原因,经常经统计一些文本内容,要去除重复,之前都是cat xx | sort | uniq 类的命令。
自从开始关注使用VIM之后,发现VIM真的很强大,直接有去重功能,怪不得这么多Linuxer都喜欢它。
我这里只说它的去重功能首先讲点基础,
awk流程是逐行处理的,默认从文件的第一行一直处理到文件最后一行,
还要知道awk的基本命令格式是'pattern{action}'先匹配各种各样的样式,然后大括号里处理如何打印输出,
默认的只要匹配了pattern就{print $0},如果pattern未命中其判断值为假(0)那么就不会再去处理{action}了
pattern命中则为判断值为真(非0)就去处理{action}。
以下操作都是在vim命令模式下的操作
复制内容到剪贴板
代码:
: sort //可以直接排序,这个太好用了
:g/^\(.*\)$\n\1$/d //去除重复行
:g/\%(^\1$\n\)\@<=\(.*\)$/d //功能同上,也是去除重复行
:g/\%(^\1\>.*$\n\)\@<=\(\k\+\).*$/d//功能同上,也是去除重复行
其实都是一些正则,但是本人正则不太好,也真的解释不清,希望有大牛能详细的解释下,每个正则是神马意思!
因为工作原因,经常经统计一些文本内容,要去除重复,之前都是cat xx | sort | uniq 类的命令,自从开始关注使用VIM之后,发现VIM真的很强大,直接有去重功能,怪不得这么多Linuxer都喜欢它。我这里只说它的去重功能
以下操作都是在命令模式下的操作
复制内容到剪贴板
代码:
: sort //可以直接排序,这个太好用了
:g/^\(.*\)$\n\1$/d //去除重复行
:g/\%(^\1$\n\)\@<=\(.*\)$/d //功能同上,也是去除重复行
:g/\%(^\1\>.*$\n\)\@<=\(\k\+\).*$/d//功能同上,也是去除重复行
其实都是一些正则,但是本人正则不太好,也真的解释不清,希望有大牛能详细的解释下,每个正则是神马意思!
-------------------------------------------------------------------------------------
awk '!a[$0]++'
而如果使用sort加uniq进行排序的话,这个文档是看不出有什么不妥。
不过我要处理的是用户名与密码一行行对应好的,如果使用sort + uniq处理的话,用户名都排到一块了,密码也又都跑到一块了。
这样就分不出来那个是那个了。
而使用的脚本很简单:
awk '!x[$0]++' filename
注:此处的x只是一个数据参数的名字而已,随你用a、b、c、d都行。
简要解释一下,awk 的基本执行流程是,对文件的每一行,做一个指定的逻辑判断,如果逻辑判断成立,则执行指定的命令;如果逻辑判断不成立,则直接跳过这一行。
我们这里写的 awk 命令是!x[$0]++,意思是:
首先创建一个 map 叫x,然后用当前行的全文$0作为 map 的 key,到 map 中查找相应的 value,如果没找到,则整个表达式的值为真,可以执行之后的语句;
如果找到了,则表达式的值为假,跳过这一行。
由于表达式之后有++,因此如果某个 key 找不到对应的 value,该++操作会先把对应的 value 设成 0,然后再自增成 1,这样下次再遇到重复的行的时候,对应的 key 就能找到一个非 0 的 value 了。
注:该处的map类似于array数组,只不过在awk中叫array不恰当。
awk Oneline中我们也学到过,awk 的流程是先判断表达式,表达式为真的时候就执行语句,可是我们前面写的这个 awk 命令里只有表达式,没有语句,那我们执行什么呢?原来,当语句被省略的时候,awk 就执行默认的语句,即打印整个完整的当前行。就这样,我们通过这个非常简短的 awk 命令实现了去除重复行并保留原有文件顺序的功能。
-------------------------------------------------------------------------------------
awk '!x[$0]++' shareprofile.txt > shareprofile_awk.txt
-------------------------------------------------------------------------------------
举个最简单的例子:awk '1' file和awk '{print $0}' file是一个道理,都是从头到尾依次打印文件的每一行
基础知识就这些,如果底太潮去man awk或者google找吧。
'!a[$0]++'
分成几个部分简单解释下吧
这个命令没有{action}也就是说,只要pattern部分判断值为真(非0)就打印正行,否则就跳过不打印
!在awk是取相反的意思,就是把对的变成错的把真的变成假的,放在这个命令中是神马作用一会解释
a[$0]这个非常好理解,建立数组a其变量是文本中的每一行,awk里$1是第一列,$2是第二列,以此类推$NF是最后一列,而$0是代表所有列及分隔符,也就是一整行,这样如果pattern是真的那就打印一整行
++的意思是a数组取变量完毕后,对该数组值+1
找个最简单的文档来解释一下
复制内容到剪贴板
代码:
cat file
xxx
yyy
xxx
zzz
这个文件有4行,其中第一、三行是重复的。套用这个命令处理流程如下
获取第一行a[xxx],因为这是第一行,数组a里从没见过xxx这个变量,那么自然他的值就是假(0)也就是说a[xxx]=0,这个时候!就有大作用了,他把a[xxx]假(0)变成了a[xxx]为真(!0)这个时候原本不改打印的第一行就变成了应该打印了,取逻辑反后对a[xxx]的值+1然后处理第二行
第二行a[yyy]这个情况跟刚才第一行的a[xxx]一样,也应该打印他
到第三行的时候情况遍了,因为第一行已经出现过了a[xxx]并且已经++过了他的值已经是非0而不是前两行的0了,本应打印但这时候再由!取逻辑反就不必打印了
第四行a[zzz]就又和第一、二两行一样了。
所以执行完就是这个结果
awk '!a[$0]++' file
xxx
yyy
zzz
再把file搞稍微复杂点
复制内容到剪贴板
代码:
awk '{print NR,$0}' file
1 xxx
2 yyy
3 zzz
4 xxx
5 yyy
6 zzz
7 xxx
8 yyy
9 zzz
一共9行文本,3行一次重复。为了看得更清楚,本来默认的{print $0}稍微改下,变成{print NR(行号),$0}。
那么现在来执行下刚才讲的试试看
复制内容到剪贴板
代码:
awk '!a[$0]++{print NR,$0}' file
1 xxx
2 yyy
3 zzz
复制内容到剪贴板
代码:
awk 'a[$0]++{print NR,$0}' file
4 xxx
5 yyy
6 zzz
7 xxx
8 yyy
9 zzz
很明显了吧,有!的命令是只打印第一次出现的$0也就是去除重复咯,而没有!的命令正好跟他相反,就是仅仅去除第一次出现的$0