Awk one-liners explained 中文版
http://bbs.chinaunix.net/thread-1640657-1-1.html
首先声明,这篇文章并不是原创,这是我在学习 awk 的过程中,经 CU 的朋
友推荐,看到了 Peteris Krumin 关于 awk 的非常精彩的讲解,由于原文是英文版
的,英语水平稍差的朋友可能学习起来会有点困难,为了能够给正在学习 awk
的朋友们提供一点点帮助,也锻炼一下自己的英语水平,我将这些 awk 讲解翻
译了一下,加上了一点个人的看法,由于本人水平有限,错误肯定不少,欢迎高
手们批评指正,共同进步。谢谢。
http://www.catonmat.net/blog/awk-one-liners-explained-part-one/ 这 是 原 文 的 链
接。。
废话不说,开始进入 awk 第一个部分:行距,编号和运算。
第一部分:行距,编号和运算
1、输出两倍行距文件
#more file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
#awk '1; { print "" }' file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
这个是如何运行的呢?每一个 awk 程序都是由一系列的 “模式—动作”语句组
成,即“模式{动作}”,在这个例子中有两个这样的语句,即"1" 和 "{ print "" }",
每个语句中无论是模式还是动作都可能不存在。如果模式部分不存在,默认匹配
所有行,那么就对每一行都执行动作。如果动作不存在,默认执行动作{print},
因此这个例子也可以写成这样:
#awk '1 { print } { print "" }' file
只有当模式正确匹配,动作才会执行,由于“1”始终是正确的,所以这个例子
可以写成两条打印的语句
#awk '{ print } { print "" }' file
在 awk 中每一条打印命令中后面都跟了一个输出记录分隔符(ORS),默认是换
行。第一个打印命令后面没有加参数,等同于{print $0},$0 是相应的每一条记录,
是可变的,通过设置输出记录分隔符(ORS)可以得到不同类型的记录。第二个
打印命令后面接的是“”(空),我们知道每个打印命令后面都跟了一个输出记录
分隔符,实际上打印了一个新行,因此打印出来以后每行之间有双倍的行距。
PS:例子中的分号的作用是将两条语句区分开来,如果不用分号 awk 会认为是
一个语句。其他两种写法不加分号是因为检测到有动作{print}存在,后面的内容
会自动认为是另一个语句。所以在 awk 中,当前一个语句只有模式而没有动作
时,后面要在加语句的话,必须要用分号区分开来,有动作时分号可有可无。
2、另一种方法输出两倍行距文件
#awk 'BEGIN { ORS="\n\n" }; 1' file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
BEGIN 是一种特殊的语句,这种语句不检测输入文件。也就是在读输入文件之前
运行。通过设置输出记录分隔符(ORS)为两次换行的方法实现输出两倍行距文
件。根据之前提到过的,语句“1”等同于{print},打印出来的每条记录之间的分
隔符都为前面设置的 ORS。
PS:一个完整的 awk 语句应该是这种形式
awk ‘BEGIN{动作};模式{动作};模式{动作}。。。;END{动作}’ file
其中 BEGIN{动作}和 END{动作}分别是在读输入文件之前和读输入文件之后执行,
通常用来制表和统计数据,很多时候都不必用到。只有中间的语句模式{动作}才
会读输入文件并对其执行动作。
3、输出两倍行距文件,并且任意两行之间只有一个空行存在。
首先修改一下 file,在里面加一个空行
#more file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
#awk 'NF { print $0 "\n" }' file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
这个命令使用了另一个 AWK 变量 NF(域的个数),意思是当前行被分割的数量,
比如 234532 23456 555555
这一行被分割成 3 个部分,因此得 NF 的值就是 3,空行的无法被分割,因此 NF
的大小就是 0.在模式中使用 NF 可以有效的过滤空行。这个命令的意思是:只要
行中存在域,就在此行的后面打印一个空行。
PS:当模式为数值(-1,0,1,1.1)时,只要数值不为 0,即为匹配所有行。
4、 三倍行距
#awk '1; { print "\n" }' file
12 32423 -2354235
234532 23456 555555
-432346 45435 4462
这个命令跟之前的很是相似,语句“1”等同于{print},因此也可以写成
#awk '{ print; print "\n" }' file
先打印一行,然后是打印输出记录分隔符(ORS),默认是换行。
PS:有些初学者在这里可能会有点疑惑(我刚开始也想了很久 O(∩_∩)O),不
过仔细想想就容易理解了。之前我们提到过,每个打印命令后面都跟了一个输出
记录分隔符(默认是换行),因此在这个命令中,先执行第一个语句:首先打印
文件的第一行,然后跟一个 ORS,也就是换行了,接着执行第二个语句{print “\n”},
\n 就是换行,这时候后面又跟一个 ORS,还是换行,因此出现了在每行之间出现
了 2 个空行。
5、给每个文件的行单独编号
#awk '{ print FNR "\t" $0 }' file
1 12 32423 -2354235
2 234532 23456 555555
3 -432346 45435 4462
这个 awk 程序在每行之前附加了一个文件行号(FNR)file line number 和一个 tab
(\t),FNR 包含了每一个文件当前行的行号。比如说,awk 针对两个文件做操作:
#awk '{ print FNR "\t" $0 }' file file
1 12 32423 -2354235
2 234532 23456 555555
3 -432346 45435 4462
1 12 32423 -2354235
2 234532 23456 555555
3 -432346 45435 4462
可以看到,结果是分别给两个文件的每一行之前加上该行在文件中的行号。FNR
给文件的行编号时,如果有多个文件会重新开始编号。
6、给所有文件的行一起编号
#awk '{ print NR "\t" $0 }' file file
1 12 32423 -2354235
2 234532 23456 555555
3 -432346 45435 4462
4 12 32423 -2354235
5 234532 23456 555555
6 -432346 45435 4462
这个命令和第五个例子几乎一样,唯一不同的地方是使用了参数(行号)NR-Line
Number。NR 与 FNR 不同的地方就在于 NR 在给多个文件的行编号的时候不会根
据文件重新编号,而是按照读取顺序统一编号。
7、花式编号
#awk '{ printf("%5d : %s\n", NR, $0) }' file
1 : 12 32423 -2354235
2 : 234532 23456 555555
3 : -432346 45435 4462
这个命令用了通常格式 printf()函数来给给行编号,像普通的 printf()函数一
样格式化参数。这里需要特别注意的是在 printf()函数后面不会附加一个输出
记录分隔符(ORS)。因此我们需要在每一行的后面明确的打印出一个换行符(\n)。
这个命令的结果是在每行之前打印行号和一个冒号。
8、只给非空行编号
#more file
12 32423 -2354235
234532 23456 555555
432346 45435 4462
#awk 'NF { $0=++a " :" $0 }; { print }'
1 :12 32423 -2354235
2 :234532 23456 555555
3 :-432346 45435 4462
Awk 参数都是动态的,在第一次使用的时候建立。这个命令指定 a 这个变量随着
行数的增加和不断自增长,空行除外(NF=0)。然后将这个参数的值和冒号附加
在每一行的开头并打印出来。
PS:在这个例子中,第一个语句是 NF{$0=++a”:”$0},模式是 NF,动作是一个
赋值语句$0=++a”:”$0,即当 NF 不为 0 的时候,在每一行的开头都加上一个变
量 a 和一个冒号,然后通过第二个语句打印出来。记住,在这里“=”不是等于
的意思,而是给$0 重新赋值,“==”才是等于的意思。
9、计算文件行数(与 wc –l 的作用类似)
#awk 'END { print NR }' file
4
前面提到过,END{}是一种不测试文件的特殊语句,它是在所有行遍历完以后执
行。在所有行遍历完以后,NR 就等于最后一行的行号,再出变量 NR 的值,就
是输出文件的行数了。
10、打印每行中域值的总和
#awk '{ s = 0; for (i = 1; i <= NF; i++) s = s+$i; print s }' file
-2321800
813543
-382449
awk 有很多地方借鉴了 c 的风格,比如这个 for(;;){„}循环。这个命令使用 for
循环遍历了每一行中的每个域(NF 即为每行中域的个数)。然后将每个域的值累
加给变量 s,最后打印出 s 的值,即为所有域相加后的值。
PS:如果把 s=s+$i 写成 s=s+i,则结果大相径庭
#awk '{ s = 0; for (i = 1; i <= NF; i++) s = s+i; print s }' file
6
6
6
因为前面的 s=$1+$2+$3,而后面的 s=1+2+3。
11、打印所有行中域值的总和
#awk '{ for (i = 1; i <= NF; i++) s = s+$i }; END { print s+0 }' file
-1890706
这个命令基本上和#10 的一致,不同的是打印出来的结果是所有域值的累加。注
意到开始没有初始化变量 s 的值为 0.这是因为要将所有行的每一个域相加的话,
就不能在遍历每一行的时候将 s 初始化为 0,否则最后得到的 s 就是最后一行的
所有域相加的值,而不是所有行。还需要注意的是最后是{print s+0}而不是{print s}。
当文件 file 中没有域(都是空行)的时候这是很有必要的。因为如果没有域的话,
那么变量 s 就无法建立也没有被定义,输出一个没有定义的变量就等于什么都不
输出(ORS 还是要跟的)。加上一个 0 的话就可以从数值上体现 s 的大小即为 0。
12、将所有域都替换成它的绝对值
#awk '{ for (i = 1; i <= NF; i++) if ($i < 0) $i = -$i; print }' file
12 32423 2354235
234532 23456 555555
432346 45435 4462
这个命令也借鉴了 c 的两个特征,if(..){„}语句和省略了大括号。这个命令遍历
了所有行中的每一个域,检查是否有小于 0 的域,如果有,将域值取反,变成整
数。域值可以使用参数间接的赋值,比如 i=5;$i=”hello”,就是将第五个域赋值
为”hello”
下面是该命令的另一种比较完整的写法,在每行中所有的域都检查过并且重新赋
值以后,再执行打印的命令。
awk '{
for (i = 1; i <= NF; i++) {
if ($i < 0) {
$i = -$i;
}
}
print
}'
13、计算一个文件中所有域的数量
#awk '{ total = total + NF }; END { print total+0 }' file
9
这个 awk 程序遍历文件 file 所有的行,并将每一行的域的数量累加起来,并将累
加的数量赋给变量 total,一旦文件遍历完,开始执行 END{},也就是打印出变量
total 的值,通过第十一个例子我们可以知道为什么打印的是 total+0。
14、打印包含“55”内容的行的个数
#awk '/55/ { n++ }; END { print n+0 }' file
1
这个命令包含两个模式{动作}语句。第一个是/55/{n++}。模式中两个斜杠之间的
是一个正则表达式。表示匹配所有包含数字“55”的行(不一定精确匹配 55 这
个数字,也匹配像 555,553,255,55d 这种)。当匹配了一行时,变量就自动+1,
第二个语句是是 END{print n+0},表示当文件遍历完后,打印出变量 n 的值。注
意到是 print n+0,因为当没有行匹配 55 的时候,n 就没有被创建和被定义,n
的值也就打印不出来,加上 0 可以避免无输出。
PS:关于正则表达式有一篇文章介绍的不错:
http://bbs.chinaunix.net/viewthread.php?tid=63273 可以参考一下。
15、找出第一个域中最大的数
#awk '$1 > max { max=$1; maxline=$0 }; END { print max"\n"maxline }' file
234532
234532 23456 555555
这个命令将通过比较将第一个域中的最大数保存在变量 max 中,并且把相对应
的行赋给变量 maxline,当所有行遍历完以后,把它们都打印出来。需要注意的
是,当第一个域中所有的域值都是负数时,这个程序无法工作。
PS:为什么第一个域都是负数的时候,打印不出来任何东西呢,是因为当第一个
域都是负数的时候,没有行能够匹配模式$1>max,因此后面的动作也就不能执
行,变量 max 和 maxline 没有被定义,因此打印不出任何东西了。
利用下面的命令可以弥补这一缺陷
#awk 'NR == 1 { max = $1; maxline = $0; next; } $1 > max { max=$1; maxline=$0 };
END { print max”\n” maxline }' file
-12
-12 32423 -2354235
这个命令在前一个命令的基础上加了一个命令 NR==1{max=$1;maxline=$0;next},
我们来看一下,模式部分是行号 NR==1,也就是说动作只对第一行做操作,把第
一个域的值赋给变量 max,第一行赋予变量 maxline。注意后面的 next 函数。next
意思就是:匹配 NR==1 的行执行完动作{max=$1;maxline=$0}后,后面的语句通通
不执行(END{}除外),也就是说后面的语句从 NR=2 开始执行。通过后面语句来
和第一行的$1 做比较,最后选出最大的$1 和相应的行并打印出来。
PS:新加的语句主要是用来初始化变量 max 和 maxline,即不管$1 的大小,直接
把$1 赋给 max,然后再比较。因此不会出现 max 没有创建和被定义的现象。
16、在每行之前打印出域的个数
#awk '{ print NF ":" $0 } ' file
3:-12 32423 -2354235
3:234532 23456 555555
3:432346 45435 4462
这个命令还是很简单了,先打印出预先确定的 NF-number of fields(域的数量),后
面加一个冒号和行记录。
17、打印每行的最后一个域
#awk '{ print $NF }' file
-2354235
555555
4462
每一行域的数量 NF 不会总是一样,$NF 就是每行的最后一个域。
18、打印最后一行的最后一个域
#awk '{ field = $NF }; END { print field }' file
4462
这个命令将记录中的最后一个域赋给变量 field,所有行遍历完以后,变量 field
的值就是最后一行记录的最后一个域了,然后打印出变量 field。
有一个更好,更常用的写法
#awk 'END { print $NF}' file
4462
19、打印超过 4 个域的行
#awk 'NF > 4' file
这个命令省略了动作只有模式,缺省的动作就是{print $0},因此如果匹配到有超
过 4 个域的行,打印出来,如果没有,则无输出。
20、打印最后一个域值大于 4 的行
#awk '$NF > 4' file
234532 23456 555555
432346 45435 4462
和前一个例子不同的地方在于,这个模式匹配的是最后一个域值大于 4 的行。并
打印相关的行。
好了,第一部分内容写完了,比较基础的东西。
第二部分是文本转换和替代,下次继续!
第一部分的还是比较简单的,从第二部分开始,就要接触到很多函数和数组
的东西,由于本人只有一点点 c 的基础,所以为了不误人子弟,希望大家能够多
提意见。谢谢。