偷得浮生半日闲,继续拜读学习awk oneliner系列的第三部分——选择性输出。该篇主要讲通过awk的正则输出特定的行的内容。其中很多是模拟grep、sed、head、tail命令的部分。由此也更能表现出awk的强大。

1、输出文件的前10行(模拟 head -n 10 )

1awk ' NR < 11 '

这里同样省略了动作,即为打印输出。匹配模式是变量NR需要小于11,NR即为当前的行号。这个写法很简单,但是有一个问题,在NR大于10的时候,awk其实还是对每行进行了判断,如果文件很大,比如说有上万行,浪费的时间是无法忽略的。所以,更好的写法是

1awk '1; NR = 10 { exit }'

第一句对当前行进行输出。第二句判断是不是已经到了第10行,如果是则退出。

2、输出文件的第一行(模拟 head -n 1 )

1awk 'NR > 1 { exit }; 1'

该句和上面的例子有点像,即NR>1时,则执行退出。

3、输出文件的最后两行(模拟 tail -n 2 )

1awk '{ y=x "n" $0; x=$0}; END { print y }'

的确,这一句看起来确实有些别扭。第一句总是把一个在当前行前面再加上变量x的内容赋值给y,然后用x记录当前行内容。这样的效果是y的内容始终是上一行加上当前行的内容。在最后,输出y的内容。如果仔细看的话,不难发现这个写法是很不高效的,因为它不停的进行赋值和字符串连接,只为了找到最后一行!所以,如果你想要输出文件的最后两行,tail -n 2是最好的选择。

4、输出文件的最后一行(模拟 tail -n 1 )

1awk 'END { print }'

句法方面没什么好说的,print省略参数即是等价于print $0。但是这个语句可能不被非GNU awk的某些awk版本正常执行,如果为了兼容,下面的写法是最安全的:

1awk '{ rec = $0 }; END { print rec }'

5、输出只匹配某些模式的行(模拟 grep )

1awk '/regex/'

7、输出匹配模式的行的上一行,而非当前行

1awk '/regex/ { print x }; { x = $0 }'

将每行数据保存在变量x中,当读到下一行的内容时,变量x此时仍存的是上行的内容。利用该特点,如果出现要匹配的内容时,我们就可以得出我们的结果——上一行的内容。

当我们在第一行的内容中出现"/regex/“中需要匹配的内容时,而我们想要给出一个提示,上面的写法显然不满足这个要求(上面的写法会输出空),这时我们可以利用下面的写法实现:

1awk '/regex/ { print (x=="" ? "match on line 1" : x) }; { x=$0 }'

注意,上面的这个脚本写的很有深度,该句使用了a?b:c的经典用法。作者举的例子是foo?bar:baz,都是一个意思。即“if foo, then bar, else baz ”

8、输出匹配模式的下一行

1awk '/regex/ { getline; print }'

这里使用了getline函数取得下一行的内容并输出。getline的作用是将$0的内容置为下一行的内容,并同时更新NR,NF,FNR变量。如果匹配的是最后一行,getline会出错,$0不会被更新,最后一行会被打印。

9、输出匹配AA或者BB或者CC的行

1awk '/AA|BB|CC/'

当然,这个语句的应用还可以更复杂一点

1awk '{if (/getpeername/||/terminfo/){print $0}if(/getuid()/){getline;print}}'

这是我在“论ssh连接的不安全性”里用的语句,如是匹配“getpeername” 或“terminfo” 则打印本行,如果匹配"getuid()“则输出下一行。提到逻辑或,不得不说下逻辑和,即 and 输出匹配同时包含AA、BB、CC的行

1awk '/AA.*BB.*CC/'

同样是正则,这里的点是连接符号。

10、输出长过65个字符的行

1awk 'length > 64'

length([str])返回字符串的长度,如果参数省略,即是以$0作为参数,括号也可以省略了。同理,我们可以输出少于65个字符的行

1awk 'length < 65'

11、输出从匹配行到最后一行的内容

1awk '/regex/,0'

这里使用了“pattern1,pattern2”的形式来指定一个匹配的范围,其中pattern2这里为0,也就是false,所以一直会匹配到文件结束。

12、输出从第8行输出到第12行

1awk 'NR==8,NR==12'

之前应该也提到过的,NR就是行号。同样输出第52行的内容可以使用:

1awk 'NR==52'

前面也有提到过,这样做会影响效率,要减少不必要的循环可以写为:

1awk 'NR==52 {print;exit}'

13、输出两次正则表达式匹配之间的行

1awk '/regex1/, /regex2/'

原文中举的例子为:

1awk '/Iowa/,/Montana/'

14、删除所有的空行

1awk NF

NF为真即是非空行。另外一种写法是用正则表达式:

1awk '/./'

这个很类似grep .的思路,但是是不如awk NF好的,因为“.”也是可以匹配空格和TAB的。