sapser's blog

Standing on the shoulders of Giants

awk '!a[$0]++'去重原理分析

Aug 7, 2014 • shell


很多人都知道awk '!a[$0]++' file可以去除文本中重复的行,但是对其到底是如何去重的却不是很清楚,所以这里就单独来分析一下这个命令。

首先我们要知道这条命令是隐含了一个print $0的,完整命令如下:

$ cat c
1
2
2
3
1
3
3
$ awk '!a[$0]++{print $0}' c
1
2
3

awk判定以下三种情况为“假”,其他情况都为“真”:

  • 数字`0`
  • 空字符串`""`
  • 未定义的变量,对于未定义的变量var,如果要进行字符串操作,会被转成空字符串`""`,如果要进行数学运算,会被转成数字`0`,也可以使用`var""`来强制转为空字符串`""`,`var+0`来强制转为数字`0`

所以上面这条awk语句的原理就是判断!a[$0]++的值,如果值为真就打印当前行,值为假自然就不会打印当前行了。

开始分析!a[$0]++吧,不过还得先看一下awk中操作符的优先级(由高到低排列):

$                #字段引用($1,$2)
++ --
^ **             #求幂
+ - !            #正、负、逻辑非       
* / %
+ -              #加法减法
(blank)          #连接符
< <= == != > >=
~ !~ 
&&
||
?:   
= += -= *= /= %= ^= **=    

这里看到++操作符优先级是高于!操作符的。

现在正式开始分析!a[$0]++,先使用{print ">"a[$0]+0}来输出每次进行!a[$0]++计算后a[$0]的值:

$ echo -e "5\n5\n5"|awk '{print ">"a[$0]+0}!a[$0]++{print $0}'
>0
5
>1
>2

为了简化输出,这里只对相同的三行进行去重,可以看到处理第一行之前a[$0]还是未定义的,所以输出为空(这里通过a[$0]+0强制转换成了0),当第一行处理完成之后a[$0]的值变成了1,第二行处理完成之后a[$0]的值变成了2,以此类推。

这里我们可以将a[$0]数组取值替换为一个简单的变量,方便理解:

$ awk 'BEGIN{a=0;print !a++,a}'
1 1
$ awk 'BEGIN{a=1;print !a++,a}'
0 2
$ awk 'BEGIN{a=2;print !a++,a}'
0 3

我们知道a++操作符是在变量a使用完之后再对变量进行自增,所以这里虽然++!优先级高,先跟变量a结合,但是不会立即自增变量a的值,而是在!a之后在自增变量a的值。通过例子来理解:

  • 变量a的值为0,`!a`取反后返回1,然后`a++`将变量a的值自增1,此时`!a++`返回的值就是`!a`计算出来的1,同时变量a的值也变为了1
  • 变量a的值为1,`!a`取反后返回0,然后`a++`将变量a的值自增1,此时`!a++`返回的值就是`!a`计算出来的0,同时变量a的值变为2
  • 变量a的值为2,`!a`取反后返回0,然后`a++`将变量a的值自增1,此时`!a++`返回的值就是`!a`计算出来的0,同时变量a的值变为3

现在是不是一目了然了,再代入回之前的例子中:

$ echo -e "5\n5\n5"|awk '{print ">"a[$0]+0}!a[$0]++{print $0}'
>0
5
>1
>2

分析如下:

  • 开始处理第一行之前,`a[$0]`是未定义的,所以值为空(相当于上面的`a=0`),`!a[$0]`取反返回1,然后`a[$0]++`将`a[$0]`的值自增1,此时`![$0]++`返回的值就是`!a[$0]`计算出来的1,数字1在awk中为真,所以会执行后面的`{print $0}`输出第一行,同时`a[$0]`的值也变为了1
  • 开始处理第二行之前,`a[$0]`值为1(相当于上面的`a=1`),`!a[$0]`取反返回0,然后`a[$0]++`将`a[$0]`的值自增1,此时`![$0]++`返回的值就是`!a[$0]`计算出来的0,数字0在awk中为假,所以不会执行后面的`{print $0}`来输出第二行,同时`a[$0]`的值变为2
  • 开始处理第三行之前,`a[$0]`值为2(相当于上面的`a=2`),`!a[$0]`取反返回0,然后`a[$0]++`将`a[$0]`的值自增1,此时`![$0]++`返回的值就是`!a[$0]`计算出来的0,数字0在awk中为假,所以不会执行后面的`{print $0}`来输出第三行,同时`a[$0]`的值变为3

分析完成,不过这只是我个人的理解,有不对的地方请回复指出,一起学习!



上一篇:关闭终端而不终止后端进程的几种方法总结

下一篇:sed地址匹配总结