格式化文本数据抽取工具awk
在管理和维护Linux系统过程中,有时可能需要从一个具有一定格式的文本(格式化文本)中抽取数据,这时可以使用awk编辑器来完成这项任务。发明这个工具的作者是Aho、Weinberg和Kernighan,取三个人名的首字母而得名awk。
与sed相比,awk更擅长处理格式化文本。格式化文本一般使用某个特定的字符(称为域分隔符)将文本中不同的字段(称为域)隔开。例如用于保存用户信息的系统用户文件/etc/passwd,该文件使用冒号分别将用户名、密码、UID等字段分隔开。
一、awk命令基本格式
- 命令格式:
awk [-F] ‘command’ input-file
awk –f script input-file
与sed类似,awk也有两种调用方式:第一种是直接使用awk命令调用,选项F用于指定域分隔符。默认情况下awk使用的域分隔符是空格,如果要处理的文件input-file的域分隔符不是空格,应该使用F选项另行指定。第二种方法跟sed一样,先将要输入的选项模式和动作放入一个脚本文件中,然后使用选项f调用。
- 命令处理过程:
awk被调用后,首先读入第一行文本并按选项F指定的域分隔符将各个字段划开。以/etc/passwd其中一行为例:
root:x:0:0:root:/root:/bin/bash
处理这个文件时,应该使用选项F指定域分隔符为冒号“:”,划分完成后将这一行称为一条记录。一条记录中的各个字段按顺序称为域1,域2,域3……为方便对这些字段进行处理,使用标识符“$1”表示第一个字段,“$2”表示第二个字段…依次类推。如果要表示整条记录,应该使用标识符“$0”。
划分记录后,awk会按预定的模式和动作处理记录。处理完一条记录后,又会读取文本第二行重复上述动作。
- 模式和动作:
一个完整的awk命令由编辑语句和格式化文本组成。编辑语句由一个或多个模式和动作组成,格式化文本即用户需要处理的文本,可以来自文件,也可以来自命令输出。
与sed一样,模式用来指定动作执行的文本位置。在awk中,模式可以是条件语句、模式匹配、正则表达式等。如果没有指定模式,awk会对所有记录执行编辑语句。
动作一般放在模式后面的大括号{}内,一般是awk的内置函数,例如输出函数print等。
下面是一个输出文件/etc/passwd中所有用户名的示例:
[root@localhost test]# awk -F: '{print "username:" $1}' /etc/passwd username:root username:bin username:daemon username:adm username:lp ...
上面这个示例命令使用选项F指定域分隔符为冒号,后面的命令中并没有指定模式,awk将默认匹配所有行。放在大括号内的动作使用了awk的内置函数print,先输出了一个字符串“username:”,然后使用标识符“$1”输出第一个字段。
- 文本头、尾表达式:
在awk中,有两个特殊的表达式BEGIN和END。BEGIN语句将在编辑语句开始执行之前运行,通常用来输出文本头信息。END语句则在所有编辑语句完成之后执行,一般用来输出结束信息和统计数据等。
使用示例文件students(见查找文本工具grep),制作一个输出学生学号和姓名,并输出文本头、文本尾的例子:
[root@localhost test]# awk 'BEGIN{print "Student ID name\n-----------------------"}{print $1"\t"$2}END{print "------------END-----------"}' students Student ID name ----------------------- 2821020225 Liulu 2821020115 Liumin 2721020321 Xuli 2921020632 Xiayu 2721010409 Liwei 2921050313 Heli 2721030227 Wangtao ------------END-----------
二、正则表达式、元字符、运算符和关系运算符
在awk中,除了可以使用正则表达式和元字符外,awk还内置了算术运算符、操作符。
- 常见的算术运算符:
=(赋值)、+(加)、-(减)、-(负)、*(乘)、/(除)、前置++、后置++、前置--、后置--、?:(条件)、%(求模)、+=(复合加)、-=(复合减)、*=(复合乘)、/=(复合除)、%=(复合求模)、^(幂)、^=(复合幂)
- 常见的关系运算符和操作符:
>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、!=(不等于)、==(等于)
~/pattern/(匹配pattern)、!~/pattern/(不匹配pattern)
- 逻辑运算符:
||(或)、&&(与)、!(非)
用法示例:
(1)结合正则表达式,输出所有2007年入学的学生信息:
[root@localhost test]# awk '/27210/{print $0}' students 2721020321 Xuli Jiangsu Luolei 12/25/92 76 81 85 79 321 80 2721010409 Liwei Sichuan tangwei 11/21/92 98 88 85 85 356 89 2721030227 Wangtao Yunnan Huli 03/21/93 87 76 69 88 320 80
(2)结合正则表达式,输出所有来自Sichuan的学生学号、姓名和总分:
[root@localhost test]# awk '/Sichuan/{print $1"\t"$2"\t"$10}' students 2821020225 Liulu 325 2721010409 Liwei 356
(3)可以指定某个域的取值精确匹配。例如要输出姓名为Liwei的学生信息:
[root@localhost test]# awk '$2=="Liwei"{print $0}' students 2721010409 Liwei Sichuan tangwei 11/21/92 98 88 85 85 356 89
(4)精确匹配时,可以使用“!=”表示不匹配。例如要输出不是来自Sichuan的学生信息:
[root@localhost test]# awk '$3!="Sichuan"{print $0}' students 2821020115 Liumin Henan lixia 05/14/94 78 65 59 78 280 70 2721020321 Xuli Jiangsu Luolei 12/25/92 76 81 85 79 321 80 2921020632 Xiayu Shanxi Hetao 03/26/93 78 86 92 78 334 84 2921050313 Heli Xizang Tangwei 07/12/94 56 78 80 45 259 65 2721030227 Wangtao Yunnan Huli 03/21/93 87 76 69 88 320 80
(5)也可以使用操作符表示匹配,例如要输出所有辅导员不是Tangwei的学生的信息:
[root@localhost test]# awk '$4 !~/[Tt]angwei/{print $0}' students 2821020225 Liulu Sichuan Lixia 01/23/93 89 76 88 72 325 81 2821020115 Liumin Henan lixia 05/14/94 78 65 59 78 280 70 2721020321 Xuli Jiangsu Luolei 12/25/92 76 81 85 79 321 80 2921020632 Xiayu Shanxi Hetao 03/26/93 78 86 92 78 334 84 2721030227 Wangtao Yunnan Huli 03/21/93 87 76 69 88 320 80
(6)也可以将匹配操作符与正则表达式一起使用。例如输出所有93年以后出生的学生信息:
[root@localhost test]# awk '$5 ~/^.......[3-9]/{print $0}' students 2821020225 Liulu Sichuan Lixia 01/23/93 89 76 88 72 325 81 2821020115 Liumin Henan lixia 05/14/94 78 65 59 78 280 70 2921020632 Xiayu Shanxi Hetao 03/26/93 78 86 92 78 334 84 2921050313 Heli Xizang Tangwei 07/12/94 56 78 80 45 259 65 2721030227 Wangtao Yunnan Huli 03/21/93 87 76 69 88 320 80
(7)使用竖线“|”匹配两边模式之一,例如要输出所有来自Sichuan和Yunnan的学生信息:
[root@localhost test]# awk '$3 ~/(Sichuan|Yunnan)/{print $0}' students 2821020225 Liulu Sichuan Lixia 01/23/93 89 76 88 72 325 81 2721010409 Liwei Sichuan tangwei 11/21/92 98 88 85 85 356 89 2721030227 Wangtao Yunnan Huli 03/21/93 87 76 69 88 320 80
(8)在使用$0标识符输出所有域时,也可以省略后面的输出动作。下面这条命令和上面的命令效果一样:
[root@localhost test]# awk '$3 ~/(Sichuan|Yunnan)/' students 2821020225 Liulu Sichuan Lixia 01/23/93 89 76 88 72 325 81 2721010409 Liwei Sichuan tangwei 11/21/92 98 88 85 85 356 89 2721030227 Wangtao Yunnan Huli 03/21/93 87 76 69 88 320 80
(9)可以使用算术运算符对域进行计算,这在制作一些统计信息时非常有用。例如要计算所有公共课的平均成绩并输出:
[root@localhost test]# awk '{print $1"\t"$2"\t"$6"\t"$7"\t"$8"\t"$6+$7+$8"\t"($6+$7+$8)/3}' students 2821020225 Liulu 89 76 88 253 84.3333 2821020115 Liumin 78 65 59 202 67.3333 2721020321 Xuli 76 81 85 242 80.6667 2921020632 Xiayu 78 86 92 256 85.3333 2721010409 Liwei 98 88 85 271 90.3333 2921050313 Heli 56 78 80 214 71.3333 2721030227 Wangtao 87 76 69 232 77.3333
(10)可以结合使用关系运算符和逻辑运算符,以实现更复杂的运算。例如要输出所有来自Sichuan并且平均分大于80的学生的信息:
[root@localhost test]# awk '($3=="Sichuan" && $11>80){print $0}' students 2821020225 Liulu Sichuan Lixia 01/23/93 89 76 88 72 325 81 2721010409 Liwei Sichuan tangwei 11/21/92 98 88 85 85 356 89
注意:使用awk时,一定要将多个模式和条件放在括号中,将执行编辑的语句放在单引号内,函数和流控制语句放入大括号内,避免产生错误。
三、在awk命令中使用变量
awk中的变量有两种:一种是内置变量,这些变量常用于控制输出和保存“awk”当前工作状态等信息,在引用变量时通常不需要使用美元符号$;另一种是用户自定义变量,这些变量由用户自己定义并使用,通常自定义变量放在BEGIN语句中初始化(赋值)。
使用自定义变量时,如果awk不能确定自定义变量的类型,通常将其默认为字符串进行处理。
- 内置变量:
FILENAME:用于保存输入文件的文件名称。
NF:用于保存当前正在处理的记录的域个数。
NR:用于保存从文本中读取记录的个数。
FNR:用于保存当前读取的记录数。当输入的文件有多个时,读取新文件时,awk会重置这个变量。
OFS:用于设置输出分隔字段的字符,默认为空格。
FS:用于设置字段分隔符。
ORS:用于设置输出记录分隔符,默认为新的一行。
RS:用于设置记录分隔符,默认为新行。
OFMT:数字的输出格式。
ENVIRON:读取环境变量。
(1)有时可能需要对输出的文本重新格式化(设置你想要的输出形式),这时可以使用内置变量OFS和ORS,设置输出文本的域分隔符和记录分隔符。
[root@localhost test]# awk 'BEGIN{FS=":"; OFS="\t"; print "NF\tNR\tusername\tshell"}{print NF,NR,$1,$7}END{print FILENAME}' /etc/passwd NF NR username shell 7 1 root /bin/bash 7 2 bin /sbin/nologin 7 3 daemon /sbin/nologin 7 4 adm /sbin/nologin 7 5 lp /sbin/nologin 7 6 sync /bin/sync ......
(2)有时会遇到一些使用特殊分隔符的文本。例如下面这个示例中,需要在命令中把FS和RS重新设置成文本中使用的的特殊分隔符(而不是默认的空格和新行)才能顺利读取文本:
[root@localhost test]# cat students3 2821020225#Liulu#0#A#B#0\ 2821020115#Liumi#B#C#0#0\ 2721020321#Xuli#0#D#A#0\ 2921020632#Xiayu#A#C#0#0\ 2721010409#Liwei#B#C#0#D\ 2921050313#Heli#B#0#D#0\ 2721030227#Wangtao#C#0#D#0\
[root@localhost test]# awk 'BEGIN{FS="#"; RS="\\n\n"}{print $1,$2}' students3 2821020225 Liulu 2821020115 Liumi 2721020321 Xuli 2921020632 Xiayu 2721010409 Liwei 2921050313 Heli 2721030227 Wangtao
(3)当输出的字符为一个浮点数时,可能需要指定浮点数的输出格式,此时可以使用内置变量OFMT。例如:
[root@localhost test]# awk 'BEGIN{OFMT="%.2f"; print 19.243564}' 19.24
提示:使用awk命令时,如果没有编辑语句,只有BEGIN语句,可以不必输入文件名直接运行。
(4)有时可能在处理过程中需要引用环境变量,此时可以使用ENVIRON变量。如下是一个使用ENVIRON读取环境变量的示例:
[root@localhost test]# awk 'END{print ENVIRON["LANG"]}' students en_US.UTF-8
上面这个例子中虽然没有引用文件中的内容,但使用了END表达式,因此必须使用文件参数,否则这条命令将变的不完整。
- 自定义变量
自定义变量通常用于统计并计算某个数字字段的结果,也可用于引用字符串。
(1)下面是一个统计当前文件夹中所有文件占用空间的示例:
[root@localhost test]# ls -l | awk 'BEGIN{A=0}{A=A+$5}END{print "The size of all files of the current directory is:"A}' The size of all files of the current directory is:13232
在上面这个示例中,首先在BEGIN表达式中定义了一个 变量A并初始化为0.然后与ls命令输出的第5个字段相加,得到所有文件占用的空间,最后在END表达式中将结果输出。
(2)变量也可以在动作语句之后添加,例如:
[root@localhost test]# awk '$2==NAME1{print $0}' NAME1="Liwei" students 2721010409 Liwei Sichuan tangwei 11/21/92 98 88 85 85 356 89
提示:awk中引用变量时,通常不需要使用引用符号。但为了便于理解和阅读,应将变量名称大写并加上引用符号。
四、在awk命令中使用流程控制
在awk命令中,还内置了一些流程控制语句(简称流控制语句)。使用这些流控制语句,可以完成更多复杂的抽取任务。
- awk中的流控制语句语法结构:
awk命令中常见的流控制语句有:if、while、do-while和for语句,这些语句的基本语法结构与c语言中的语句类似。
(1)if语句的基本格式:
if (条件表达式) { 语句块1 } else { 语句块2 }
(2)while语句基本格式:
while (条件表达式) { 语句块 }
(3)do-while语句基本格式:
do { 语句块 } while (条件表达式)
(4)for语句基本格式:
for (初始表达式;条件表达式;步长) { 语句块 }
(5)控制语句:
continue:用于在循环语句中控制循环走向。执行到此语句时,立即从当前执行位置跳转到循环开始处开始下一次循环。此语句用于标识后面的语句已经没有必要执行了,必须立即开始下一次循环执行过程。
break:用于跳出循环。当执行到此语句时,循环立即停止,然后执行循环后面的语句。
next:使awk读取文本的下一行。该语句告诉awk,当前行已经没有任何价值,需要立即读取文本的下一行。
exit:如果这条语句没有出现在END表达式中,则立即跳转去执行END中的语句(即直接跳过文件最后一行,执行END表达式中的语句)。如果出现在END表达式中,则awk命令立即停止执行并退出。
提示:由于循环语句会导致命令变得很长,且影响阅读,因此通常建议将循环语句放入脚本文件中执行。
流控制语句用法示例:
(1)例如要输出文件students中,所有总成绩320以上的学生信息:
[root@localhost test]# awk 'BEGIN{OFS="\t"}{if($10>=320) print $1,$2,$10}' students 2821020225 Liulu 325 2721020321 Xuli 321 2921020632 Xiayu 334 2721010409 Liwei 356 2721030227 Wangtao 320
(2)输出来自Sichuan的学生中,总成绩大于等于320的学生信息:
[root@localhost test]# awk 'BEGIN{OFS="\t"}{if($3=="Sichuan" && $10>=320) print $1,$2,$10}' students 2821020225 Liulu 325 2721010409 Liwei 356
(3)计算ls命令传送过来的信息中,所有普通文件的大小和文件夹的个数:
[root@localhost test]# cat total.awk BEGIN{ #Initialize A and count. A=0; count=0; } { #if the first character of field 1 is '-', then add A and the value of field 5. if($1 ~/^-/) A+=$5; #if the first character of field 1 is 'd', then count plus 1. if($1 ~/^d/) count++; } END{ print "total:"A; #count-2:because there are current directory and up level directory. print count-2," directories."; }
[root@localhost test]# ls -al /etc | awk -f total.awk total:1423743 84 directories.
(4)Linux系统管理员经常需要关注网络延时大小,以免对应用服务的正常运作造成影响。最好的解决办法是每隔一段时间,使用ping命令测试与某个服务器的连接,并统计延时。
下面是一个使用ping命令发送4个ICMP数据包测试双向连通性,并用awk从中计算最大延时、最小延时和平均延时的示例:
[root@localhost test]# cat ping.awk BEGIN{ #使用重新定义变量FS的方法设置域分隔符为冒号、等号及空格 FS="[:= ]"; AVG=0; MAX=0; MIN=0; } { for(I=1;I<9;I++) { #如果当前为第二条记录,则为变量MAX、MIN、IP_ADDR赋值 if(NR==2) { MAX=$11; MIN=$11; IP_ADDR=$4; } if(NR>1 && NR<6) { #计算延时总和 AVG+=$11; if($11>MAX) MAX=$11; if($11<MIN) MIN=$11; } #如果记录数大于6,则跳出循环并执行END中的表达式 if(NR>=6) exit; #否则,读取系一条记录,重新开始循环 next; } } END{ AVG=AVG/4; print "IP address:",IP_ADDR; print "Avg:",AVG,"ms"; print "Max:",MAX,"ms"; print "Min:",MIN,"ms"; }
为了方便抽取数据,先在BEGIN语句中定义了3中域分隔符:冒号、等号和空格。
由于ping命令返回的第1行是由ping自动生成的简要提示信息,因此awk将忽略第1行。
第2个到第5个记录返回了TTL值和延时等信息,后面则是一些统计信息,因此awk处理过程中只处理第2个到第5个记录。
五、awk命令中的函数
awk命令中的函数分为两种:内置函数和用户自定义函数。内置函数多是一些用来处理字符串和数学运算的函数,用户自定义函数则是由用户自己定义的功能性函数。
- 内置函数:
awk命令中函数的用法与c语言中函数的用法十分相似。常见的内置函数如下:
sub(regexp, replacement [, target]):当模式regexp第1次出现时,用replacement替换。
gsub(regexp, repalcement [, target]):将与模式regexp匹配的内容全部用replacement替换。
index(string, find):返回与find匹配的字符串第1次在string中出现的位置。
length([string]):返回字符串string的长度。
match(string, regexp [, array]):返回与regexp字符串第1次匹配的位置,如果不存在则返回0.
split(string, array [, fieldsep]):将string按照字段分隔符filedsep进行分割,分割后的字段存入数组array,返回字符串数组元素个数。
printf(format, expr1 [, expr2, …]):格式化输出函数。
substr(string, start, [, length]):从string中返回从start开始,长度为length的子字符串。
tolower(string):将string中的大写字符转换成小写。
toupper(string):将string中的小写字符转换成大写。
print:输出函数。
rand():返回一个0到1之间的随机数。
system():将传递来的字符串按shell命令执行。
atan2(x, y):反正切函数。
cos(x):余弦函数。
sin(x):正弦函数。
log(x):x的自然对数值。
exp(x):E的x次幂。
int(x):将x转换为整数类型。
sqrt(x):x的平方根。
用法示例:(示例文件students2.2内容如下)
[root@localhost test]# cat students2.2 2821020225 Liulu 0 A B 0 2821020115 Liumin B C 0 0 2721020321 Xuli 0 0 B D 2921020632 Xiayu 0 0 0 0 2721010409 Liwei C 0 0 0 2921050313 Heli A 0 A B 2721030227 Wangtao B B C 0
(1)使用sub函数可以查找第1次出现的模式并替换。例如用sub函数修改匹配模式的记录:
[root@localhost test]# awk '$1 ~/28210202/{print sub(/Liulu/,"Lilu",$0)"\n"$0}' students2.2 1 2821020225 Lilu 0 A B 0
在上面的命令执行后,在第1行返回了修改的次数(对应sub函数),第2行输出了修改后的记录(对应$0)。
(2)利用函数gsub将所有匹配到的大写字母A替换成数字95,然后返回一共修改的次数:
[root@localhost test]# awk 'BEGIN{N=0}{N+=gsub(/A/,"95",$0); print $0}END{print N}' students2.2 2821020225 Liulu 0 95 B 0 2821020115 Liumin B C 0 0 2721020321 Xuli 0 0 B D 2921020632 Xiayu 0 0 0 0 2721010409 Liwei C 0 0 0 2921050313 Heli 95 0 95 B 2721030227 Wangtao B B C 0 3
由于函数gsub执行一个记录的替换就会返回一次,因此此处用N+=gsub()。否则只会返回最后一条记录替换的次数。
(3)利用index函数可以返回第1次匹配字符串的位置:
[root@localhost test]# awk '{print index($0, "L"); if(NR==2) exit}' students2.2 12 12
(4)利用length函数查看字符串长度:
[root@localhost test]# awk '{print length($0); if(NR==2) exit}' students2.2 25 26
(5)使用match函数查看匹配模式首次出现的位置:
[root@localhost test]# awk 'BEGIN{print match("Hello! Welcome to Beijing!", "B")}' 19
(6)利用split函数将字符串按指定的分隔符拆开,然后放入指定的数组中并返回数组的元素个数:
[root@localhost test]# awk 'BEGIN{print split("FV7H8-42D17-08D0Q-8QAG9-YPUZA",ARR,"-");for(I in ARR)print ARR[I]}' 5 8QAG9 YPUZA FV7H8 42D17 08D0Q
另一种方式:
[root@localhost test]# awk 'BEGIN{AN=split("FV7H8-42D17-08D0Q-8QAG9-YPUZA",ARR,"-");print AN;for(I=1;I<=AN;I++)print ARR[I]}' 5 FV7H8 42D17 08D0Q 8QAG9 YPUZA
注意:数组下标从1开始。
提示:在awk中使用数组时,可以不用先定义,也不必指出元素的个数。
(7)利用tolower函数将字符串中的大写字符转换成小写:
[root@localhost test]# awk '$1=="2821020225"{print tolower($0)}' students2.2 2821020225 liulu 0 a b 0
(8)利用toupper函数将字符串中的小写字符转换成大写:
[root@localhost test]# awk '$1=="2821020225"{print toupper($0)}' students2.2 2821020225 LIULU 0 A B 0
(9)利用rand函数返回一个0到1之间的随机数:
[root@localhost test]# awk 'BEGIN{print rand()}' 0.237788
(10)sin、cos函数可以计算正弦、余弦:
[root@localhost test]# awk 'BEGIN{PI=3.1415926;print sin(PI/4),cos(PI/4)}' 0.707107 0.707107
(11)使用log函数可以返回自然对数:
[root@localhost test]# awk 'BEGIN{print log(100)}' 4.60517
(12)exp函数可以返回e的x次幂:
[root@localhost test]# awk 'BEGIN{print exp(256)}' 1.51143e+111
(13)sqrt函数可以计算平方根:
[root@localhost test]# awk 'BEGIN{print sqrt(256)}' 16
(14)格式化输出函数printf(与c函数里的printf一样),可以使用修饰符对输出的内容进行格式控制,其基本语法如下:
print format, [argument]…
printf常用的修饰符:
%c:作为一个ASCII字符输出。
%s:作为一个字符串输出。
%o:作为一个八进制数输出。
%x:作为一个十六进制输出。
%d:作为一个整数输出。
%e:作为一个科学型浮点数输出。
%f:作为一个浮点型数字输出。
%g:由awk决定浮点数输出的形式。
除了以上一些修饰符外,awk也允许像c语言那样使用“-”、域宽和“.”进一步细化输出格式。
使用printf函数将数字100分别转换成字符、八进制、十六进制数:
[root@localhost test]# awk 'BEGIN{A=100;printf "%c\n%o\n%x\n",A,A,A}' d 144 64
(15)使用printf的修饰符可以将生成的数据转换成相应的格式:
[root@localhost test]# awk 'BEGIN{printf "%e\n%f\n%g\n",exp(30),exp(30),exp(30)}' 1.068647e+13 10686474581524.462891 1.06865e+13
(16)有时在计算一个数字时要保留一定的精度,这时可以使用“.”指定精度,例如:
[root@localhost test]# awk 'BEGIN{printf "%4.5f\n",sqrt(47)}' 6.85565
(17)从/etc/passwd中抽取用户名和用户使用的shell,使用%s对齐输出:
[root@localhost test]# awk 'BEGIN{FS=":";printf "%-15s %-15s\n","username","shell";printf "------------------------------\n"}{printf "%-15s %-15s\n",$1,$7}' /etc/passwd username shell ------------------------------ root /bin/bash bin /sbin/nologin daemon /sbin/nologin adm /sbin/nologin lp /sbin/nologin ...
(18)、为了使输出的段对齐,这里使用修饰符,例如:
[root@localhost test]# awk '{printf "%-10s\t%-10s\t%d\t%d\t%d\t%d\t%d\n",$1,$2,$6,$7,$8,$6+$7+$8,($6+$7+$8)/3}' students 2821020225 Liulu 89 76 88 253 84 2821020115 Liumin 78 65 59 202 67 2721020321 Xuli 76 81 85 242 80 2921020632 Xiayu 78 86 92 256 85 2721010409 Liwei 98 88 85 271 90 2921050313 Heli 56 78 80 214 71 2721030227 Wangtao 87 76 69 232 77
- 用户自定义函数
在awk命令中,还允许使用用户自定义函数。与C语言中的函数作用范围不同,自定义函数无论出现在命令中的什么位置,作用都是全局的,而函数中定义的变量只作用于函数内部。如果需要从函数中返回一个值,可以使用return语句。
在awk命令中自定义函数的基本格式:
function name([参数列表]) { 语句块; [return ...]; }
在调用函数时,只需要使用函数名并加上要使用的参数列表即可,无须使用特别的语句。
(1)下面是一个计算文件students中学生公共课总成绩和平均成绩的例子:
[root@localhost test]# cat ex.awk function Add(A,B,C) { return A+B+C; } function Avg(A,B,C) { return (A+B+C)/3; } { printf "%-10s\t%-10s\t%d\t%d\t%d\t%d\t%d\n",$1,$2,$6,$7,$8,Add($6,$7,$8),Avg($6,$7,$8); } END{ printf "%s\n",FILENAME; } [root@localhost test]# awk -f ex.awk students 2821020225 Liulu 89 76 88 253 84 2821020115 Liumin 78 65 59 202 67 2721020321 Xuli 76 81 85 242 80 2921020632 Xiayu 78 86 92 256 85 2721010409 Liwei 98 88 85 271 90 2921050313 Heli 56 78 80 214 71 2721030227 Wangtao 87 76 69 232 77 students
(2)对students中的学生成绩进行筛选,只输出学号,学生姓名和课程中的最高成绩并保存为strudents_max.
为实现上述功能,此处可以使用条件三目运算符和数组两种方法。为此因此两个脚本文件stu.awk1和stu.awk2:
[root@localhost test]# cat stu.awk1 function Max(Arr) { Ma=Arr[1]; for(I in Arr) { if(Ma<Arr[I]) Ma=Arr[I]; } return Ma; } { for(J=1;J<5;J++) Ar[J]=$(J+2); printf "%-10s\t%-10s\t%d\n",$1,$2,Max(Ar); } [root@localhost test]# cat stu.awk2 function Max(A,B,C,D) { A>B?A:A=B; C>D?C:C=D; Ma=(A>C)?A:C; return Ma; } { printf "%-10s\t%-10s\t%d\n",$1,$2,Max($3,$4,$5,$6); }
使用awk的选项f调用stu.awk脚本,并将结果显示并保存到students_max中:
[root@localhost test]# awk -f stu.awk1 students | tee students_max 2821020225 Liulu 89 2821020115 Liumin 78 2721020321 Xuli 85 2921020632 Xiayu 92 2721010409 Liwei 98 2921050313 Heli 80 2721030227 Wangtao 88 [root@localhost test]# awk -f stu.awk2 students | tee students_max2 2821020225 Liulu 89 2821020115 Liumin 78 2721020321 Xuli 85 2921020632 Xiayu 92 2721010409 Liwei 98 2921050313 Heli 80 2721030227 Wangtao 88
注意:在C语言中A>B?A:(A=B)可以简写成A>B?:(A=B),但在awk中这是不允许的。
(3)最后,引用一个使用awk监视磁盘并向用户发出警告的例子。在这个例子中,当某一个文件系统空闲不足10%时,通过system函数运行mail命令向管理员发出警告信息。
使用的脚本和执行过程如下:
[root@localhost test]# cat df.awk function mess(A,B,C) { if(B>=0.9) { system("date +'%F %r'>/root/df.tmp; df -h>>/root/df.tmp; cat /root/df.tmp | mail -s 'Disk Warning' 1025399680@qq.com,root;rm -rf /root/df.tmp"); printf "Disk Warning!\nFile System:%s\nUsed:%3.0f\nMounted on:",A,B*100,C; } } { if($1 ~/^\/dev\/sd/) { N=$3/$2; if(N>=0.9) mess($1,N,$6); } }
[root@localhost test]# df | awk -f df.awk
注意:在调用函数system时,一定要将shell命令放入双引号中,shell命令原本应放入双引号中的参数此时应该放入单引号中。