awk函数
awk内置了大量的函数可供我们直接调用实现更丰富的功能,同时还允许自定义函数。下面为大家介绍一些常用的内置函数,以及如何编写自定义函数。
1. 内置I/O函数
getline函数可以让awk立刻读取下一行数据(读取下一条记录并复制给$0,并重新设置NF、NR和FNR)。
在有些使用了逻辑卷分区的Linux系统中,通过df输出文件系统信息时,逻辑卷分区的信息往往都是跨行显示,而普通的分区在可以一行显示一个分区的信息,这样当我们需要提取分区的磁盘空间容量时,就会有字段列数不一致的问题。
[root@centos7 ~]# df -hFilesystem Size Used Avail Use% Mounted on/dev/mapper/VolGroup00-LogVol0019G 3.6G 15G 21% //dev/sda1 99M 14M 81M 15% /boottmpfs 141M 0 141M 0% /dev/shm
可以很明显地看出上面df命令的输出结果中,逻辑卷分区的信息是跨行显示,而普通的sda1分区信息仅使用一行即可显示完整数据。此时,如果我们需要提取所有磁盘剩余空间,直接打印$4肯定是不可以的!
[root@centos7 ~]# df -h | awk \'{print $4}\'Avail21%81M141M
df命令输出的第二行仅包含一列数据,因此打印$4返回的就是空白,而第三行数据的第四列是21%的磁盘使用率,只有到后续的普通磁盘分区打印$4可以是正确地输出了磁盘剩余容量。
我们需要判断当读取某一行数据只有一列时,执行getline函数直接读取下一行的数据,然后再打印第三列磁盘剩余容量的数值,而如果读取的数据行包含六列数据则直接打印输出第四列磁盘剩余容量的数值。
[root@centos7 ~]# df -h | awk \'{ if(NF==1){getline;print $3} ; if(NF==6) {print $4} }\'15G81M141M
上面这条命令执行流程如下:
1)读取第一行数据,该行数据包括七个字段,与NF==1和NF==6都不匹配,因此不打印任何数据。
2)读取第二行数据,该行数据包括一个字段,与NF==1匹配,因此先执行getline,没有执行getline之前$0的值是/dev/mapper/VolGroup00-LogVol00,执行getline之后,$0被重新复制为"19G 3.6G 15G 21% /",此时再print $3打印第三列刚好是磁盘的剩余空间15G。
3)读取第三行数据,该行数据包括六个字段,与NF==6匹配,因此直接输出第四列数值。
4)读取第四回数据,该行数据包含六个字段,与NF==6匹配,因此直接输出第四列数值。
通过在getline之前和之后分别打印输出$0,可以观察出getline对当前行数据$0的影响。
[root@centos7 ~]# df -h | awk \'NR==2{print $0;getline;print $0}\'/dev/mapper/VolGroup00-LogVol0019G 3.6G 15G 21% /
next函数可以停止处理当前的输入记录,并立刻读取下一条记录并返回awk程序的第一个模式匹配重新处理数据。getline函数仅仅是读取下一条数据,而不会影响后续awk指令的执行,但是next不仅读取下一行数据,而且会导致后续的指令都不再执行,而是重新读取数据后重新回到awk指令的开始位置重新匹配重新执行动作指令。
[root@centos7 ~]# vim test.txt #新建素材文件Plants are living things.Plants need sunshine, air and water.I like to read and draw.How many boxes are there?[root@centos7 ~]# awk \'/air/{getline;print \"next line:\",$0} {print \"noraml line\"}\' test.txtnoraml linenext line: I like to read and draw.noraml linenoraml line[root@centos7 ~]# awk \'/air/{next;print \"next line:\",$0} {print \"noraml line\"}\' test.txtnoraml linenoraml linenoraml line
对比上面两条命令的区别,对于无法正则匹配/air/的行都不会执行getline或next指令,都是执行print "noraml line",对于匹配了正则条件/air/的行,如果是执行getline函数则并不影响后续的print "next line:",$0指令的指令,而如果执行的是next函数,则通过最终的输出结果可知next后续的print指令都不在执行,而是跳回到awk的开始重新匹配条件执行动作指令。
[root@centos7 ~]# awk \'/Plants/{next;print \"next line:\",$0} {print \"noraml\"}\' test.txtnoramlnoraml[root@centos7 ~]# awk \'/Plants/{getline;print \"next line:\",$0} {print \"noraml\"}\' test.txtair next line: Plants need sunshine, air and water.noramlnoramlnoraml
system(命令)函数可以让我们在awk中直接调用Shell命令。awk会启动一个新的shell进程执行命令。
[root@centos7 ~]# awk \'BEGIN{system(\"ls\")}\'anaconda-ks.cfg test.txt[root@centos7 ~]# awk \'BEGIN{system(\"echo test\")}\'test[root@centos7 ~]# awk \'BEGIN{system(\"uptime\")}\'23:08:47 up 1:10, 1 user, load average: 0.00, 0.01, 0.05[root@centos7 ~]# awk \'{system(\"echo date:\"$0)}\' test.txtdate:Plants are living things.date:Plants need sunshine, air and water.date:I like to read and draw.date:How many boxes are there?[root@centos7 ~]# awk \'{system(\"echo date:\"$0 \" >> /tmp/test\")}\' test.txt[root@centos7 ~]# cat /tmp/testdate:Plants are living things.date:Plants need sunshine, air and water.date:I like to read and draw.date:How many boxes are there?
2. 内置数值函数
cos(expr)函数返回expr的cosine值。
[root@centos7 ~]# awk \'BEGIN{print cos(50)}\'0.964966[root@centos7 ~]# awk \'BEGIN{print cos(10)}\'-0.839072[root@centos7 ~]# awk \'BEGIN{print cos(180)}\'-0.59846sin (expr)函数返回expr的sine值。[root@centos7 ~]# awk \'BEGIN{print sin(90)}\'0.893997[root@centos7 ~]# awk \'BEGIN{print sin(45)}\'0.850904sqrt(exp)返回可以对expr计算平方根。[root@centos7 ~]# awk \'BEGIN{print sqrt(2)}\'1.41421[root@centos7 ~]# awk \'BEGIN{print sqrt(4)}\'2[root@centos7 ~]# awk \'BEGIN{print sqrt(8)}\'2.82843int(expr)取整函数,仅截取整数部分数值。[root@centos7 ~]# awk \'BEGIN{print int(6.8)}\'6[root@centos7 ~]# awk \'BEGIN{print int(6.1)}\'6[root@centos7 ~]# awk \'BEGIN{print int(6.13453)}\'6[root@centos7 ~]# awk \'BEGIN{print int(6)}\'6[root@centos7 ~]# awk \'BEGIN{print int(3.13453)}\'3rand()函数可以返回0到1之间的随机数N(0<=N<1)。[root@centos7 ~]# awk \'BEGIN{print rand()}\'0.237788[root@centos7 ~]# awk \'BEGIN{for(i=1;i<=5;i++)print rand()}\'0.2377880.2910660.8458140.1522080.585537[root@centos7 ~]# awk \'BEGIN{for(i=1;i<=5;i++)print 100*rand()}\'23.778829.106684.581415.220858.5537[root@centos7 ~]# awk \'BEGIN{for(i=1;i<=5;i++)print int(100*rand())}\'2329841558srand([expr])函数可以使用expr定义新的随机数种子,没有expr时则使用当前系统的时间为随机数种子。[root@centos7 ~]# awk \'BEGIN{print rand()}\' #没有新种子随机数固定0.237788[root@centos7 ~]# awk \'BEGIN{print rand()}\'0.237788[root@centos7 ~]# awk \'BEGIN{print rand()}\'0.237788[root@centos7 ~]# awk \'BEGIN{srand();print rand()}\' #使用时间做随机数种子0.325548[root@centos7 ~]# awk \'BEGIN{srand();print rand()}\'0.769117[root@centos7 ~]# awk \'BEGIN{srand();print rand()}\'0.769117[root@centos7 ~]# awk \'BEGIN{srand(777);print rand()}\' #使用数值做随机数种子0.478592[root@centos7 ~]# awk \'BEGIN{srand(777);print rand()}\'0.478592[root@centos7 ~]# awk \'BEGIN{srand(888);print rand()}\'0.850364[root@centos7 ~]# awk \'BEGIN{srand(99);print rand()}\'0.508567
3. 内置字串函数
length([s])函数可以统计字符串s的长度,如果不指定字符串s则统计$0的长度。
[root@centos7 ~]# awk \'BEGIN{test=\"hello the world\";print length(test)}\'15[root@centos7 ~]# awk \'BEGIN{t[0]=\"hi\";t[1]=\"the\";t[2]=\"world\";print length(t)}\'3 #返回数组元素个数(长度)[root@centos7 ~]# awk \'BEGIN{t[0]=\"hi\";t[1]=\"the\";t[2]=\"world\";print length(t[0])}\'2 #返回t[0]值的长度[root@centos7 ~]# awk \'BEGIN{t[0]=\"hi\";t[1]=\"the\";t[2]=\"world\";print length(t[1])}\'3 #返回t[1]值的长度[root@centos7 ~]# awk \'BEGIN{t[0]=\"hi\";t[1]=\"the\";t[2]=\"world\";print length(t[2])}\'5 #返回t[1]值的长度[root@centos7 ~]# cat /etc/shells/bin/sh/bin/bash/sbin/nologin/usr/bin/sh/usr/bin/bash/usr/sbin/nologin[root@centos7 ~]# awk \'{print length()}\' /etc/shells #返回文件每行的字符长度7913111317index(字串1,字串2)函数返回字串2在字串1中的位置坐标。[root@centos7 ~]# awk \'BEGIN{test=\"hello the world\";print index(test,\"h\")}\'1 #h在test变量的第一个位置[root@centos7 ~]# awk \'BEGIN{test=\"hello the world\";print index(test,\"t\")}\'7 #t在test8000变量的第七个位置[root@centos7 ~]# awk \'BEGIN{test=\"hello the world\";print index(test,\"w\")}\'11[root@centos7 ~]# awk \'BEGIN{print index(\"Go to meat section\",\"t\")}\'4[root@centos7 ~]# awk \'BEGIN{print index(\"Go to meat section\",\"o\")}\'2match(s,r)函数根据正则表达式r返回其在字串s中的位置坐标。[root@centos7 ~]# awk \'BEGIN{print match(\"How much? 981$\",\"[0-9]\")}\'11 #数字在第十九个位置出现[root@centos7 ~]# awk \'BEGIN{print match(\"How much? 981$\",\"[a-z]\")}\'2[root@centos7 ~]# awk \'BEGIN{print match(\"How much? 981$\",\"[A-Z]\")}\'1tolower(str)函数可以将字符串转换为小写。[root@centos7 ~]# awk \'BEGIN{print tolower(\"THIS IS A TEst\")}\'this is a test[root@centos7 ~]# awk \'BEGIN{apple=\"ReD APPle\";print tolower(apple)}\'red apple[root@centos7 ~]# awk \'BEGIN{apple[0]=\"ReD APPle\";print tolower(apple[0])}\'red appletoupper(str)函数可以将字符串转换为大写。[root@centos7 ~]# awk \'BEGIN{apple[0]=\"red aPPle\";print toupper(apple[0])}\'RED APPLE[root@centos7 ~]# awk \'BEGIN{apple=\"red aPPle\";print toupper(apple)}\'RED APPLE[root@centos7 ~]# awk \'BEGIN{print toupper(\"this is a test\")}\'THIS IS A TESTsplit(字串,数组,分隔符)函数可以将字串按特定的分隔符切片后存储数组中,如果没有指定分隔符则使用FS定义的分隔符进行字串切割。[root@centos7 ~]# awk \'BEGIN{split(\"hello the world\",test);print test[3],test[2],test[1]}\'world the hello这条命令以空格或Tab键为分割,将hello the world切割为独立的三个部分,分别存入test[1],test[2],test[3]数组中,最后通过print指令可以按照任意顺序打印显示这些数组元素的值。[root@centos7 ~]# awk \'BEGIN{split(\"hello:the:world\",test);print test[1],test[2]}\' hello:the:world #test[2]为空值[root@centos7 ~]# awk \'BEGIN{split(\"hello:the:world\",test,\":\");print test[1],test[3]}\'hello world #自定义分隔符为冒号[root@centos7 ~]# awk \'BEGIN{split(\"hi8the9world3!\",test,\"[0-9]\");print test[3],test[4]}\'world ! #使用正则定义分隔符gsub(r,s,[,t])函数可以将字符串t中所有与正则表达式r匹配的字串全部替换为s,如果没有指定字串t的话,默认对$0进行替换操作。[root@centos7 ~]# awk \'BEGIN{hi=\"hello world\";gsub(\"o\",\"O\",hi);print hi}\'hellO wOrld #所有o替换为O[root@centos7 ~]# awk \'BEGIN{hi=\"Hello World\";gsub(\"[a-z]\",\"*\",hi);print hi}\'H**** W**** #所有小写字母替换为星[root@centos7 ~]# awk \'BEGIN{hi=\"Hello World\";gsub(\"[A-Z]\",\"*\",hi);print hi}\'*ello *orld #所有大写字母替换为星[root@centos7 ~]# head -1 /etc/passwdroot:x:0:0:root:/root:/bin/bash[root@centos7 ~]# head -1 /etc/passwd | awk \'{gsub(\"[0-9]\",\"**\");print $0}\'root:x:**:**:root:/root:/bin/bashsub(r,s,[,t])函数与gsub类似,但仅替换第一个匹配的字串,而不是替换全部。[root@centos7 ~]# head -1 /etc/passwd | awk \'{sub(\"[0-9]\",\"**\");print $0}\'root:x:**:0:root:/root:/bin/bash[root@centos7 ~]# head -1 /etc/passwd | awk \'{sub(\"root\",\"XX\");print $0}\'XX:x:0:0:root:/root:/bin/bash[root@centos7 ~]# awk \'BEGIN{hi=\"Hello World\";sub(\"[A-Z]\",\"*\",hi);print hi}\'*ello Worldsubstr(s,i[,n])函数可以对字串s进行截取,从第i位开始,截取n个字串,如果n没有指定则一直截取到字串s的末尾位置。[root@centos7 ~]# awk \'BEGIN{hi=\"Hello World\";print substr(hi,2,3)}\'ell #从第二位开始截取3个字符[root@centos7 ~]# awk \'BEGIN{hi=\"Hello World\";print substr(hi,2)}\'ello World #从第二位开始截取到末尾
4. 内置时间函数
systemtime()返回当前时间距离1970-01-01 00:00:00有多少秒。
[root@centos7 ~]# awk \'BEGIN{print systime()}\'1556205414[root@centos7 ~]# awk \'BEGIN{print systime()}\'1556205416
5. 用户自定义函数
awk用户自定义函数格式如下。
function 函数名(参数列表) { 命令序列 }
[root@centos7 ~]# awk \'function myfun() { print \"hello\"} BEGIN{ myfun() }\'hello[root@centos7 ~]# awk \' \\function max(x,y) { \\if(x>y) {print x} \\else {print y} } \\BEGIN { max(8,9) }\'9
上面的命令首先定义了一个名称为myfun的函数,函数体内只有一条指令,就是打印输出hello。然后因为是在BEGIN{}中调用的myfun函数,因此该函数仅被执行一次。
[root@centos7 ~]# awk \' \\function max(x,y) { \\if(x>y) {print x} \\else {print y} } \\BEGIN{ max(18,9) }\'18
上面这个示例定义了一个可以接受传递参数(简称传参)的函数,定义函数时定义了两个形式参数x和y(简称形参),在调用函数时再输入实际参数(简称实参),max(18,9)中的18和9就是实参,按照前后顺序,awk会在调用执行函数时将18赋值给变量x,9赋值给变量y。这样我们就可以将对比最大值或其他类似的功能写成一个函数,这种函数可以反复被调用,每次调用都可以传递不同的参数,对比不一样的数字大小。我们也可以设计一些功能更加强大的函数,这个就需要根据实际的应用环境和案例灵活应变了。