很多人都知道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
>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
分析完成,不过这只是我个人的理解,有不对的地方请回复指出,一起学习!