23.Linux awk命令详解

 

 

Linux awk命令:

AWK是一种处理文本文件的语言,是一个强大的文本分析工具。我们可以利用awk命令,把一个文本整理成我们想要的样子。例如可以整理成表的样子。awk其实是一门编程语言,支持判断、数组、循环等功能。

 

语法:

awk [选项参数] 'script' var=value file(s)
awk [选项参数] -f scriptfile var=value file(s)
awk [options] 'pattern{action}' file

参数解释:

用法一:

awk [options] 'pattern{action}' file

action指的是动作,awk最常用的动作就是print 和 printf 。

1、不指定pattern,使用最简单的action。执行一个打印动作

[root@dm shell]# echo hello world! > test.log
[root@dm shell]# awk '{print}' test.log
hello world!

输出df信息的第5列,使用df | awk '{print $5}' 。$5表示将当前行按照分隔符分割后的第五列。默认空格作为分隔符,awk自动将连续的空格作为一个分隔符。

 
[root@dm shell]# df
文件系统             1K-块      已用      可用 已用% 挂载点
/dev/sda3             28696576   9926076  17312788  37% /
tmpfs                   436768        76    436692   1% /dev/shm
/dev/sda1               198337     32627    155470  18% /boot
[root@dm shell]# df | awk '{print $5}'
已用%
37%
1%
18%
[root@dm shell]# df | awk '{print $4,$5}'
可用 已用%
17312780 37%
436692 1%
155470 18%
 

awk是逐行处理的,awk处理文本是一行一行处理的,默认以换行符为标记识别每一行。新的一行开始,awk以用户指定的分隔符处理新的一行。没有指定默认空格作为分隔符。每个字段按照顺序,分别对应到awk的内置变量中。分割完后的第一个字段为$1,第二个字段为$2..... ,类推。$0表示当前处理的整个一行。$NF表示当前行分割后的最后一列。

注意:$NF和NF不一样,$NF表示分割后的最后一列,NF表示当前行被分割后一共有几列。

示例:

 
[root@dm shell]# cat test.log
abc 123 ide wdw
dwf 23d dw3 45f w2e

1、一次输出多列,中间用逗号分隔
[root@dm shell]# awk '{print $1, $3}' test.log
abc ide
dwf dw3

2、一次输出多列,某行没有指定的列,不输出。第一行没有第五列
[root@dm shell]# awk '{print $1, $3, $5}' test.log
abc ide 
dwf dw3 w2e

3、awk添加字段,或者将自己的字段与文件中的列相结合
[root@dm shell]# awk '{print $1, $2, "string"}' test.log
abc 123 string
dwf 23d string
[root@dm shell]# awk '{print $1, $2, 666}' test.log
abc 123 666
dwf 23d 666
[root@dm shell]# awk '{print "diyilie:"$1, "dierlie:"$2}' test.log
diyilie:abc dierlie:123
diyilie:dwf dierlie:23d
[root@dm shell]# awk '{print "diyilie:"$1, "666", "dierlie:"$2}' test.log
diyilie:abc 666 dierlie:123
diyilie:dwf 666 dierlie:23d
 

 2、使用模式pattern。先介绍两种特殊模式,BEGIN  END。BGEIN模式指定处理文本之前需要执行的操作,END模式表示处理完所有行之后执行的操作。

 
[root@dm shell]# awk 'BEGIN{print "aaa", "bbb"}'
aaa bbb
[root@dm shell]# awk 'BEGIN{print "aaa", "bbb"}' test.log
aaa bbb
[root@dm shell]# awk '{print $1, $2}END{print "ccc","ddd"}' test.log
abc 123
dwf 23d
ccc ddd
[root@dm shell]# awk 'BEGIN{print "aaa", "bbb"}{print $1, $2}END{print "ccc","ddd"}' test.log
aaa bbb
abc 123
dwf 23d
ccc ddd
 

上述代码返回的结果就像一个表,有表头,表内容,表尾。体验了awk对文本的格式化能力。

awk分隔符:

awk默认以空格作为分隔符,但是这样描述是不准确的。awk分为输入分隔符和输出分隔符两种。输入分隔符简称FS,默认空白字符(即空格)awk默认以空白字符对每一行进行分割。输出分隔符,简称OFS,awk将每行分割后,输出到屏幕上以什么字符作为分割。默认的输出分隔符也是空格。

输入分隔符

当awk逐行处理文本时,以输入分隔符为准,将文本切分为多个字段,默认使用空格。如果一段文字里没有空格,还可以指定特定的字符或符号作为分隔符。我们可以使用 -F 选项,指定 # 作为分隔符。

[root@dm shell]# cat test.log
abc#123#ide#wdw
dwf#23d#dw3#45f#w2e
[root@dm shell]# awk -F# '{print $1, $3}' test.log
abc ide
dwf dw3

除了使用 -F选项指定分隔符,我们还可以通过FS这个内置变量来指定输入分隔符。这里后文再详解。

输出分隔符

当awk为我们输出每一列时候,会使用空格隔开每一列,这个空格就是awk默认的输出分隔符。我们可以使用awk的内置变量OFS设置输出分隔符,使用变量的时候要配合 -v 选项。示例如下:

[root@dm shell]# awk -F# -v OFS="---" '{print $1, $2}' test.log
abc---123
dwf---23d
[root@dm shell]# awk -v FS="#" -v OFS="---" '{print $1, $2}' test.log
abc---123
dwf---23d

在输出的时候,如果想将两列合并到一起。即不使用分隔符。可以省区在字段之间的逗号,这样输出的字段就连在一起了。

awk '{print $1 $2}'表示分割后,将第一列和第二列合并到一起输出

awk '{print $1, $2}'表示分割后,将第一列和第二列以输出分隔符分开后显示

awk变量:

awk变量分为内置变量和自定义变量,上文提到的输入分隔符FS和输出分隔符OFS都是内置变量。以下列出awk常用的内置变量:

FS : 输入字段分隔符,默认空白符

OFS : 输出字段分隔符,默认空白符

RS :  输入记录分隔符(输入换行符),指定输入时的换行符

ORS : 输出记录分隔符(输出换行符),输出时用指定符号代替换行符

NF : number of filed 当前行的字段个数(即当前行被分割成几列)

NR :行号,当前处理文本行的行号

FNR : 各文件分别计数的行号

FILENAME :当前文件名

AGRC :命令行参数的个数

ARGV : 数组,保存命令行所给定的各参数

(1)、内置变量NR、NF,NR表示每一行的行号,NF表示一样有几列。NR、NF的使用参考如下示例:

 
1、文件test.log共有两行,第一行四列,第二行五列
[root@dm shell]# cat test.log 
abc 123 ide wdw
dwf 23d dw3 45f w2e
2、输出行号和每行的列数
[root@dm shell]# awk '{print NR, NF}' test.log 
1 4
2 5
3、输出行号和文本内容
[root@dm shell]# awk '{print NR, $0}' test.log 
1 abc 123 ide wdw
2 dwf 23d dw3 45f w2e
 

(2)、内置变量FNR,awk处理多个文件时,如果使用NR显示行号,那么多个文件的所有行会按照顺序进行排序。如果想要分别显示两个文件的行号,这时候要用到内置变量FNR,示例如下:

 
[root@dm shell]# awk '{print NR, $0}' test.log test1.log 
1 abc 123 ide wdw
2 dwf 23d dw3 45f w2e
3 abc#123#ide#wdw
4 dwf#23d#dw3#45f#w2ie
[root@dm shell]# awk '{print FNR, $0}' test.log test1.log 
1 abc 123 ide wdw
2 dwf 23d dw3 45f w2e
1 abc#123#ide#wdw
2 dwf#23d#dw3#45f#w2ie
 

(3)、内置变量RS,RS是行分隔符。如果不指定,行分隔符就是默认的回车符。如果不想使用默认的回车换行,想使用空格作为换行符。可以理解为妹遇到一个空格就换一行,示例如下:

 
[root@dm shell]# cat test.log 
abc 123 ide wdw
dwf 23d dw3 45f w2e
[root@dm shell]# awk '{print NR, $0}' test.log 
1 abc 123 ide wdw
2 dwf 23d dw3 45f w2e
[root@dm shell]# awk -v RS=" " '{print NR, $0}' test.log 
1 abc
2 123
3 ide
4 wdw
dwf
5 23d
6 dw3
7 45f
8 w2e
 

在指定了空格作为换行符后,awk认为空格换行,但是回车符不再换行。所以在第4行中,人为看到的换行了,但是在awk的世界观中并没有换行,都属于第四行。

(4)、内置变量ORS,输出行分隔符。默认也是回车符作为输出行分隔符。如果我们使用+++作为输出行分隔符,示例如下:

[root@dm shell]# awk -v ORS="+++" '{print NR, $0}' test.log 
1 abc 123 ide wdw+++2 dwf 23d dw3 45f w2e+++[root@dm shell]#

把RS和ORS一起使用,在文件中的空格输入到awk,awk认为是换行符。awk输出时,认为换行符是+++。可以这么理解,文件里的空格最终输出后是+++

[root@dm shell]# awk -v RS=" " -v ORS="+++" '{print NR, $0}' test.log 
1 abc+++2 123+++3 ide+++4 wdw
dwf+++5 23d+++6 dw3+++7 45f+++8 w2e

 (5)、内置变量FILENAME,就是显示文件名,示例如下:

[root@dm shell]# awk '{print FILENAME, FNR, $0}' test.log test1.log 
test.log 1 abc 123 ide wdw
test.log 2 dwf 23d dw3 45f w2e
test1.log 1 abc#123#ide#wdw
test1.log 2 dwf#23d#dw3#45f#w2ie

(6)、内置变量ARGC和ARGV。ARGV内置变量表示一个数组,这个数组中保存命令行给定的参数,示例如下:

[root@dm shell]# awk 'BEGIN{print "aaa"}' test test1
aaa
[root@dm shell]# awk 'BEGIN{print "aaa", ARGV[0], ARGV[1], ARGV[2]}' test test1
aaa awk test test1

所以,ARGV表示的是所有参数组成的数组。第一个参数是awk命令本身,'pattern{action}'不被看作参数。在上边的例子中,awk test test1 三个参数作为数组元素被放到数组中。而ARGC表示参数的数量,也可以理解为ARGV参数的长度。

[root@dm shell]# awk 'BEGIN{print "aaa", ARGV[0], ARGV[1], ARGV[2], ARGC}' test test1
aaa awk test test1 3

(7)自定义变量,就是用户定义的变量。有两种方法。

方法一:-v varname=value 变量名区分大小写

方法二:在program中直接定义

通过方法一自定义变量,与设置内置变量的方法是一样的。当我们需要在awk中引用shell中的变量时,可以通过方法一间接引用

 
1、自定义变量
[root@dm shell]# awk -v myVar="testVar" 'BEGIN{print myVar}'
testVar
2、引用shell中变量
[root@dm shell]# abc=6666666
[root@dm shell]# awk -v myVar=$abc 'BEGIN{print myVar}'
6666666
 

使用方法二定义,直接在程序中定义即可。变量定义和动作之间要用分号隔开,还可以定义多个变量

[root@dm shell]# awk 'BEGIN{myvar="ttt"; print myvar}'
ttt
[root@dm shell]# awk 'BEGIN{myvar1="aaa"; myvar2="bbb"; print myvar1, myvar2}'
aaa bbb

 awk格式化能力

在上文中我们已经体验到了awk的格式化能力。在上文我们通常使用print对文本进行输出,但是print动作只能进行简单的文本输出功能。如果需要改变文本的格式,就需要printf命令。关于printf命令的使用方法请参考(https://www.cnblogs.com/jkin/p/10758802.html

 

利用awk的printf动作,即可对文本进行格式化输出。下边我们用一个简单的例子,看一下print动作和printf动作的区别

 
[root@localhost shell]# clear
[root@localhost shell]# cat test
aaa bbb 123 cdf
swe ef4 fw2 fer4 fve
[root@localhost shell]# awk '{print $1}' test
aaa
swe
[root@localhost shell]# awk '{printf $1}' test
aaaswe[root@localhost shell]#
 

由例子可以看出,printf动作和printf命令一样,都不输出换行符,默认输出文本在一行里面。但是printf动作既然和printf命令用法一样,printf动作也肯定由格式替换符了。我们就可以用格式替换符来替换一下$1了,示例如下:

[root@localhost shell]# awk '{printf "%s\n", $1}' test
aaa
swe

看起来printf动作和printf命令使用方法是一样的,但是仔细看会发现,printf动作和printf还是存在唯一的不同点。在使用printf动作时,指定的格式与列($1)之间需要使用逗号隔开。在使用printf命令时,指定的格式和传入的文本不需要使用逗号隔开。示例如下所示:

[root@localhost shell]# awk '{printf "%s\n", $1}' test
aaa
swe
[root@localhost shell]# printf "%s\n" aaa swe
aaa
swe

其实他们还有一些不同之处,在使用printf命令时,当指定的格式中只有一个格式替换符,但是传入了多个参数,那么这个参数可以重复使用这一个格式替换符。但是在awk里边不能这么用,在awk中,格式替换符的数量必须与传入参数的数量相同。也就是说,格式替换符必须与需要格式化的参数一一对应。示例如下:

 
[root@localhost shell]# printf "%s\n" 1 2 3 4
1
2
3
4
[root@localhost shell]# awk 'BEGIN{printf "%s\n", 1, 2, 3, 4}'
1
[root@localhost shell]# awk 'BEGIN{printf "%s\n%s\n%s\n%s\n", 1, 2, 3, 4}'
1
2
3
4
 

总结,在awk中使用printf动作时,需要注意以下几点

(1)、使用printf动作输出的文本不会换行,如果需要换行,可以在对应的格式替换符后边加 \n 进行转义

(2)、使用printf动作时,指定的格式与被格式化的文本之间,需要用逗号隔开

(3)、使用printf动作时,格式中的格式替换符必须与被格式的文本一一对应

 练手小例子:

我们可以利用格式替换符对文本中的每一列进行格式化:

[root@localhost shell]# cat test
aaa bbb 123 cdf
swe ef4 fw2 fer4 fve
[root@localhost shell]# awk '{printf "第一列:%s   第二列:%s\n", $1, $2}' test
第一列:aaa   第二列:bbb
第一列:swe   第二列:ef4

上述例子完美的展现了awk对文本格式化的能力。awk本身负责文本切割,printf负责文本格式化。

我们还可以利用awk的begin模式,结合printf动作,输出一个表格

 
[root@localhost shell]# awk -v FS=":" 'BEGIN{printf "%-10s\t %s\n", "用户名称","用户ID"} {printf "%-10s\t %s\n", $1,$3}' /etc/passwd 
用户名称           用户ID
root           0
bin            1
daemon         2
adm            3
lp             4
sync           5
shutdown       6
 

 awk模式解析

在刚开始介绍awk命令的时候,我们已经介绍了两种模式BEGIN和END。此处,我们将详细的介绍以下awk中的模式。模式这个词听上去不容易被理解,我们这里换一个说法,把模式换成条件。我们知道awk时逐行处理文本的,也就是说,awk处理完当前行,再处理下一行。如果我们不指定任何条件,awk会逐行处理文本中的每一行。但是如果我们指定了条件,只要满足条件的行会被处理,不满足条件的行不会被处理。这就是awk中的模式。

 

awk处理文本时,会把pattern(模式)作为条件,判断将要被处理的行是否满足条件。是否能跟模式进行匹配,如果匹配则进行处理,不匹配不进行处理。我们通过一个例子来理解:

文本test1有三行,第一行有4列,第二行有5列,第三行有2列。

[root@localhost shell]# cat test1 
aaa bbb 123 cdf
swe ef4 fw2 fer4 fve
111 222
[root@localhost shell]# awk 'NF==5 {print $0}' test1 
swe ef4 fw2 fer4 fve

以上例子使用了一个简单的模式,也可以理解为我们使用了一个条件。这个条件就是如果被处理的行刚好是5列。那被处理的行就满足条件,满足条件的行会执行相应的动作。即打印当前行,只要第二行有5列,所以输出了第二行。举一反三:

 
[root@localhost shell]# cat test1 
aaa bbb 123 cdf
swe ef4 fw2 fer4 fve
111 222
[root@localhost shell]# awk 'NF>2 {print $0}' test1 
aaa bbb 123 cdf
swe ef4 fw2 fer4 fve
[root@localhost shell]# awk 'NF<=4 {print $0}' test1 
aaa bbb 123 cdf
111 222
[root@localhost shell]# awk '$1=="aaa" {print $0}' test1 
aaa bbb 123 cdf
 

上面的模式都有一个共同点,就是在上述模式中,都使用了关系运算符。当经过关系运算得出得结果为真时,则满足条件,然后执行相应得动作。我们总结一下awk支持的关系运算符。

关系运算符                     含义                     用法示例                    
< 小于 x < y
<= 小于等于 x <= y
== 等于 x == y
!= 不等于 x != y
>= 大于等于 x >= y
> 大于 x > y
~ 与对应正则匹配则为真                           x ~/正则/
!~                                                    与对应正则不匹配则为真 x !~/正则/                                             

我们把用到了关系运算符的模式称之为:关系表达式模式活着关系运算符模式

其实在学习模式之前,我们一直都在使用模式。之前我们没有指定模式的时候,其实也是一中模式,称之为空模式。空模式会匹配文本中的每一行。即每一行都满足条件。所以每一行都会执行相应的动作。目前位置,我们已经接触了,空模式、关系运算模式、BEGIN/END模式、这三种。

正则模式

 正则模式,顾名思义,正则模式和正则表达式有关。正则模式可以理解为,把正则表达式当条件。能与正则匹配的行,就算满足条件。满足条件的行才会执行相应的动作。不能被正则匹配的行,则不会执行相应的动作。我们通过一个例子来理解:

[root@localhost shell]# grep "^ro" /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@localhost shell]# awk '/^ro/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash

如上例所示,我们通过grep命令配合正则表达式找到了我们所需要的信息。同样的通过awk命令一样找到了我们所需要的信息,grep命令和awk命令使用了相同的正则表达式"^ro" 。 唯一的区别时,grep命令直接使用正则表达式,而在awk命令中,正则表达式被放在了两个斜线中。在上例中,grep看起来更简单一些,但是awk的优势在于它强大的格式化能力。

 
[root@localhost shell]# awk -v FS=":" 'BEGIN{printf "%-10s%10s\n","用户名","用户ID"} /^s/{printf "%-15s%10s\n", $1,$3}' /etc/passwd
用户名             用户ID
sync                    5
shutdown                6
systemd-network       192
sshd                   74
 

从这个例子可以看出,该例子使用BGEIN模式生成了表头,使用正则模式把/etc/passwd文件中以字母s打头的用户名和用户ID筛选出来。使用awk一条命令完成了多项工作。

需要注意的是,在使用正则模式时,如果正则中包含"/",则需要进行转义,我们通过一个例子来理解。

[root@localhost shell]# grep "/bin/bash$" /etc/passwd
root:x:0:0:root:/root:/bin/bash
dmdba:x:1000:1000::/home/dmdba:/bin/bash
[root@localhost shell]# awk '/\/bin\/bash$/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
dmdba:x:1000:1000::/home/dmdba:/bin/bash

如上所示,使用/bin/bash作为shell的用户被我们找了出来。但是因为正则中包含 / 。awk使用正则模式时,又需要把正则放到两个 / 中,所以需要转义。初次之外,还有两点需要注意:

(1)、在awk命令中使用正则模式时,使用到的正则用法属于"扩展正则表达式"。

(2)、当使用{x,y}这种次数匹配的正则表达式时,需要配合--posix或者--re-interval选项。(centos7貌似目前没有这个注意事项了)

行范围模式

上文中介绍了正则模式,理解了正则模式。再理解行范围模式,就容易多了。我们通过一个问题来引出行范围模式。

假设一个文本,内容如下:

 
[root@localhost shell]# cat -n test3 
     1    Allen Phillips
     2    Green Lee
     3    William Aiden James Lee
     4    Angel Jack
     5    Tyler Kevin
     6    Lucas Thomas
     7    Kevin
 

如上所示,Lee这个名字出现了两次,第一次出现再第二行。Kevin这个名字也出现了两次,第一次出现在第五行。如果想从上述文本中找出,从Lee第一次出现的行,到Kevin第一次出现的行之间的所有行。我们通过awk的行范围模式,可以实现以上需求。

[root@localhost shell]# awk '/Lee/,/Kevin/{print $0}' test3
Green Lee
William Aiden James Lee
Angel Jack
Tyler Kevin

我们来解释一下行范围模式的语法。

awk '/正则/ {动作}'  /some/file

awk '/正则1/,/正则2/ {动作}'  /some/file

上边第一个属于正则模式的语法,第二个属于行范围模式的语法。它表示,从被正则1匹配到的行开始,到被正则2匹配的行结束,之间的所有行都执行相应的动作。所以,这种模式被成为行范围模式。它对应的是一个范围内的所有行。需要注意的一点是,在行范围模式中,不管是正则1还是正则2,都以第一次匹配到的为准。

有时,你可能会有这样的需求。不想依靠正则表达式去匹配行的特征,想打印第X行到第Y行之间的所有行。这时候其实我们可以通过前边讲的关系运算符模式来实现,示例如下:

[root@localhost shell]# awk 'NR>3 && NR<6 {print $0}' test3
Angel Jack
Tyler Kevin

其他

在上文讲关系表达式模式时,有一个关系运算符的表格。有一个关系运算符需要和正则配合,它就是 ~ 。我们通过一个例子来理解一下 ~ 和  !~ 运算符。

 
[root@localhost shell]# cat test4 
主机名   网卡1的IP        网卡2的IP
主机A    192.168.2.123    192.168.1.124
主机B    192.168.2.222    172.16.200.2
主机C    10.1.0.1         172.16.100.3
主机D    10.1.2.1         192.168.1.60
主机E    10.1.5.1         172.16.100.5
主机F    192.168.1.234    172.16.100.6
主机G    10.1.7.1         172.16.100.7
[root@localhost shell]# awk '$2~/192\.168\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1,$2}' test4 
主机A 192.168.2.123
主机B 192.168.2.222
主机F 192.168.1.234
 

上述示例需求是在test4文本中找出,网卡1的IP地址在192.168网段的主机。$2为内置变量,表示文本中第二列。"$2~/正则/"表示文本中第二列如果和正则匹配。则执行对应的动作。

到目前为止,我们已经认识了awk的模式,模式可以分为以下五种

(1)、空模式

(2)、关系运算模式

(3)、正则模式

(4)、行范围模式

(5)、BEGIN/END模式

 awk动作解析

我们从一个小例子来分析动作, awk '{print $0}'  file 

如上例所示, '{print $0}' 就是我们所说的动作,想必大家一定很熟悉了。该动作的最外层是括号 {} ,内层是print $0。之前我们一直把它当成一个动作。现在我们把它拆开来理解。其实,被查开这两部分都可以称之为动作,只不过它们属于不同类型的动作而已。

print 属于输出语句类型的动作。顾名思义,输出语句类型动作的作用就是输出,打印信息。print和printf都属于输出语句类型的动作。

{}也被称为动作。 {}属于组合语句类型的动作,组合语句类型的动作就是将多个代码组合成代码块。我们通过一个示例来理解下:

 
[root@localhost shell]# cat test5
f s
1 2
2 1
[root@localhost shell]# awk '{print $1} {print $2}' test5
f
s
1
2
2
1
[root@localhost shell]# awk '{print $1; print $2}' test5
f
s
1
2
2
1
 

如上所示,当我们使用了两个大括号。他们属于组合类型的动作,分别将两个print括住,表示这两个print动作分别是独立的个体。也就是说,一共有四个动作,两个括号和两个print。每个大括号中只有一个动作。但是组合语句动作的作用是将多个代码块组合成一个整体,如上第三条命令所示,只使用了一个大括号,就可以将两个print动作组合成一个整体。每段动作之间需要用分号隔开。

除了输出动作和组合语句动作之外。当然还有其他动作,现在我们认识一下另一种动作,它就是控制语句。第一个控制语句就是条件判断。也就是编程语法中的if判断语句。

if(条件)
{
语句1
语句2
...
}

在awk中,同样可以使用if语句进行条件判断。只不过在命令行的awk中,我们要将上例中的多行语句写在一行中。示例如下:

[root@localhost shell]# awk '{if(NR==1){print $0}}' test5
f s

 上例中即为条件判断的语法。if(NR==1),NR是内置变量表示行号,所以,该条件表示的是当行号为1时,条件成立。即该动作表示的是打印文本的第一行。

如上例,为什么最外层需要一个大括号?没有原因,必须这么写,否则报错。可以这么理解,所以的动作最外层必须用大括号 {} 括起来。那么if的语法结构里边也包含大括号。if里边的大括号是否属于组合语句?我们可以这么理解,if语句仍然属于控制语句,控制语句中包含组合语句。if语句的大括号中,也可以执行多个动作,把多个代码段当成一个整体。也就是说,如果if条件成立,则执行if大括号里的所有动作,示例如下:

[root@localhost shell]# awk '{if(NR==1){print $1; print $2}}' test5 
f
s

上例中,如果行号为1,则满足条件。就会执行if对应大括号的所有代码。两个print动作一起执行,如果不成立,两个动作都不执行。该例中,if对应的大括号里边有多条语句。所有if语法中的大括号不能省略。如果if对应的大括号里边只有一条命令,那么if对应的大括号可以省略。

[root@localhost shell]# awk '{if(NR==1)print $0}' test5 
f s

如上所示,如果if对应的大括号里边只有一条命令,那么if对应的大括号可以省略。如果条件成立,需要执行多条语句,大括号不可以省略。上例还可以通过模式NR==1来实现,虽然语法不同,但是结果是一样的。

编程语言中,除了if判断,还有if...else..语法和if...else if...else...语法

 
if(条件)
{
语句1;
语句2;
...
}
else
{
语句1;
语句2;
...
}

if(条件1)
{
语句1;
语句2;
...
}
else if(条件2)
{
语句1;
语句2;
...
}
else
{
语句1;
语句2;
...
}
 

其实,这些语法在编程语言中都是相同的。我们直接来看示例:

 在/etc/passwd文件的第三列存在的是用户ID。在centos6中,ID小于500的是系统用户,大于500的是普通用户。centos7中,ID小于1000的是系统用户,大于1000的是普通用户。我们以centos7为例,用awk找出哪些是系统用户,哪些是普通用户。

 
[root@localhost shell]# awk -v FS=":" '{if($3<1000){printf "%-10s","系统用户";print $1}else{printf "%-10s","普通用户";print $1}}' /etc/passwd
系统用户      root
系统用户      bin
系统用户      daemon
系统用户      adm
系统用户      lp
系统用户      sync
系统用户      shutdown
系统用户      halt
系统用户      mail
系统用户      operator
系统用户      games
系统用户      ftp
系统用户      nobody
系统用户      systemd-network
系统用户      dbus
系统用户      polkitd
系统用户      postfix
系统用户      sshd
系统用户      chrony
普通用户      dmdba
 

我们再看一下 if...else if...else的例子

 
[root@localhost shell]# cat test6 
姓名      年龄
苍井空    32
吉泽明步  28
樱井莉亚  18
[root@localhost shell]# awk 'NR!=1 {if($2<20){print $1,"年轻人"}else if($2<30){print $1,"中年人"}else{print($1,"老年人")}}' test6
苍井空 老年人
吉泽明步 中年人
樱井莉亚 年轻人
 

 上文我们介绍了控制语句中的条件判断。除了条件判断,控制语句还包括循环。awk中也有for循环和while循环,我们先来看一下循环语句的语法:

 
#for循环语法格式1
for(初始化; 布尔表达式; 更新) {
//代码语句
}
 
#for循环语法格式2
for(变量 in 数组) {
//代码语句
}
 
#while循环语法
while( 布尔表达式 ) {
//代码语句
}
 
#do...while循环语法
do {
//代码语句
}while(条件)
 

现在我们通过一个示例来了解一下循环语句,因为还没有介绍数组,我们先演示上述语法中格式一的语法:

[root@localhost shell]# awk 'BEGIN{for(i=1;i<=3;i++){print i}}' 
1
2
3

上例中,我们使用了BEGIN模式,BEGIN模式对应的动作中,包含了for循环语句。和其他语言中的for循环几乎没什么区别,只不过写在了一行而已。

再来看一下while的具体使用,为了方便演示,仍然使用BEGIN模式。示例如下:

 
[root@localhost shell]# awk -v i=1 'BEGIN{while(i<=3){print i;i++}}'
1
2
3
[root@localhost shell]# awk 'BEGIN{i=1;while(i<=3){print i; i++}}'
1
2
3
 

当while对应的条件满足时,则执行对应的语句。语句执行完成后,对条件进行修改。

同理,do...while的示例如下,它与while循环的不同之处在于。while循环当满足条件时才会执行对应语句,而do...while无论条件是否满足,都会先执行一遍do里边的代码。然后再判断是否满足while中对应的条件,满足条件,则执行do对应的代码。如果不满足条件,则不再执行do对应的代码。

 
[root@localhost shell]# awk 'BEGIN{i=1;do{print "test"; i++}while(i<1)}'
test
[root@localhost shell]# awk 'BEGIN{do{print "test"; i++}while(i<=3)}'
test
test
test
test
 

如上所示,无论是否满足while中的条件,都会先执行一遍do对应的代码。

提到了循环,就必须说说跳出循环的语句。和其他编程语言一样,在awk中,同样使用break和continue跳出循环。

continue的作用,跳出当前循环

break的作用,跳出整个循环

我们先来看一个continue的例子,示例如下:

 
[root@localhost shell]# awk 'BEGIN{for(i=1;i<6;i++){print i}}'
1
2
3
4
5
[root@localhost shell]# awk 'BEGIN{for(i=1;i<6;i++){if(i==3){continue};print i}}'
1
2
4
5
 

由于在for循环中添加了条件判断,所以当i等于3时。跳过了当前本次循环。没有执行当前循环需要打印的动作,所以上例中数字3没有被打印出来。如果想结束的风彻底,可以用break结束循环:

[root@localhost shell]# awk 'BEGIN{for(i=1;i<6;i++){if(i==3){break};print i}}'
1
2

continue和break同样可以用于while和do...while循环。在shell脚本中,我们肯定用过exit命令。在shell中,exit表示退出当前脚本。在awk中,它的含义是一样的。它表示不再执行awk命令。相当于退出了当前的awk命令。示例如下:

[root@localhost shell]# awk 'BEGIN{print 1; print 2; print 3}'
1
2
3
[root@localhost shell]# awk 'BEGIN{print 1; exit; print 2; print 3}'
1

如上例所示,在第一条命令中,执行了多个动作。在第二条命令中,也执行了多条动作。但当在awk中执行了exit语句时,之后的动作就不再执行了,想当于退出了awl命令。

其实这样描述exit并不完全准确,因为,当在AWK中使用了END模式后,exit的作用并不是退出awk命令,而是直接执行END模式中的动作,示例如下:

 
[root@localhost shell]# cat test7 
1
2
3
[root@localhost shell]# awk 'BEGIN{print "start"}{print $0}END{print "over"}' test7 
start
1
2
3
over
[root@localhost shell]# awk 'BEGIN{print "start"; exit}{print $0}END{print "over"}' test7 
start
over
 

如上例所示,在awk命令中使用了END模式后。如果执行了exit语句,那么exit语句之后的动作就不再执行。直接执行END模式中的动作。

在awk中,除了可以使用exit命令结束awk命令。还可以使用next命令结束当前行,什么意思呢?在前边我们提到,awk时逐行处理文本的。awk会处理完当前行在处理下一行。那么,当awk处理的某行时,我们可以告诉awk这一行不用处理了,直接处理下一行。这时候就可以使用next命令来实现这个需求。换句话说,next命令可以使awk不对当前行执行对应的动作。而是直接处理下一行。示例如下:

 
[root@localhost shell]# awk '{print $0}' test7 
1
2
3
[root@localhost shell]# awk '{if(NR==2){next};print $0}' test7 
1
3
 

其实,next和continue有点类似,只是continue是针对循环而言的,next是针对逐行处理而言的。

到此,awk常用的流程控制语句与循环语句都已经总结完毕了。

 awk数组详解

在其他编程语言中,都有数组的概念。我们可以通过数组的下标,引用数组中的元素。在其他语言中,通常数组的下标都是从0开始。awk也是支持数组的。但是在awk中,数组的下标是从1开始的。但是为了兼容使用习惯。我们也可以从0开始设置下标,到后边你就会明白,我们先来看一个示例。在其他语言中,一般都需要先声明一个数组。但是在awk中不需要声明,直接给数组中得元素赋值即可。示例如下:

[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; print huluwa[1]}'
二娃

如上例所示,在BEGIN模式中,存了一个数组。放置了三个元素。想引用第二个元素的值,只要引用下标为1的元素即可。当前我们可以在数组中放置更多的元素。如果命令太长,可能回影响阅读性。我们可以使用Linux命令行的换行符进行换行,Linux的命令换行符为反斜线 \  。

[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]="六娃"; print huluwa[5]}'
六娃
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]=""; print huluwa[5]}'

上例中第二条命令,六娃的本来是隐身。我们把第六个元素设置为空字符串来代表隐身。当打印第六个元素时,打印出的值就是空(注:空格不为空)。举这个例子,是为了说明在awk中,元素的值可以设置为空,在awk中将元素的值设置为空是合法的。

既然awk中的元素可以为空,那么就不可以根据元素的值是否为空去判断该元素是否存在了。所以你通过以下办法来判断一个元素是否存在是不正确的,示例如下:

[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]=""; if(huluwa[5]==""){print "第六个元素不存在"}}'
第六个元素不存在

上例中,第六个元素是存在的,但是通过上述方法判断元素是否存在时,显示是不存在。

其实,通过空字符串判断一个元素是否存在之所以不合理,除了上述原因之外。还有一个原因,就是当一个元素不存在与数组时,如果我们直接引用这个不存在的元素。awk会自动创建这个元素,并且默认这个元素为空字符串。示例如下:

 
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]=""; print huluwa[6]}'

[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]=""; if(huluwa[6]==""){print "第七个元素不存在"}}'
第七个元素不存在
[root@localhost shell]#
 

如上例第一个命令所示,数组中没有第七个元素。但是当我们输出第七个元素时,输出了空。那么,我们应该如何判断一个元素在数组中是否存在呢。我们可以使用这样的语法 if(下标 in 数组名) 。从而判断数组中是否存在对应的元素。

[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]=""; if(5 in huluwa){print "第六个元素存在"}}'
第六个元素存在
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]=""; if(6 in huluwa){print "第七个元素存在"}}'
[root@localhost shell]#

当然我们还可以使用  ! 对条件进行取反,awk中数组的下标不仅可以是数字,还可以是任意字符串。如果使用过shell中的数组,你可以把awk的数组比作shell中的关联数组。

 
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]=""; if(!(6 in huluwa)){print "第七个元素不存在"}}'
第七个元素不存在
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa["wuwa"]="五娃"; huluwa[5]=""; print huluwa["wuwa"]}'
五娃
[root@localhost shell]#
 

其实,awk本身就是关联数组。最开始以数字最为下标,是因为数字容易理解。awk默认会把数字下标转换为字符串。所以,它本质上还是一个使用字符串为下标的关联数组。

使用delete可以删除数组中的元素,也可以删除整个数组,示例如下:

 
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]="六娃"; print huluwa[4]; delete huluwa[4]; print huluwa[4]}'
五娃

[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; huluwa[5]="六娃"; print huluwa[4]; delete huluwa; print huluwa[1]; print huluwa[3]}'
五娃


[root@localhost shell]#
 

到目前为止,我们已经介绍了怎么给数组中的元素赋值,输出某个元素,删除某个元素等。那么在awk中,想要输出所有元素呢?我们可以借助前边提到的for循环语句。我们回顾一下for循环语句

 
#for循环语法格式1
for(初始化; 布尔表达式; 更新) {
//代码语句
}
 
#for循环语法格式2
for(变量 in 数组) {
//代码语句
}
 

以上两种for循环,都可以遍历数组中的元素。不过第一种for循环只能输出数字作为下标的数组。如果当数组的下标是无规则的字符串时,我们可以使用第二种for循环方式,示例如下:

 
[root@localhost DAMENG]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; for(i=0;i<=4;i++){print huluwa[i]}}'
大娃
二娃
三娃
四娃
五娃
[root@localhost DAMENG]# awk 'BEGIN{huluwa["yi"]="大娃"; huluwa["er"]="二娃"; huluwa["san"]="三娃"; huluwa["si"]="四娃";\
huluwa["wu"]="五娃"; for(i in huluwa){print i, huluwa[i]}}'
wu 五娃
san 三娃
yi 大娃
er 二娃
si 四娃
 

 如上所示,第一种循环利用for中的变量i和数组中的下标都是数字这一特性,按照顺序输出了数组中的元素值。当数组下标是无规律的字符串时,我们就可以采用第二种方法。在第二种方式中,变量i表示的是元素的下标,并非元素的值。

你一定发现了,当数组中的下标为字符串的时候。元素值得输出顺序与元素在数组中的顺序不同。这是因为awk中的数组本身是关联数组,所以默认打印出是无序的。那么为什么使用数字作为下标就是有序的呢,数字作为下标最终也会转换为字符串,本质也是关联数组。那是因为的一种方式变量i为数字,i是按顺序递增的。即使使用数字作为下标,采用第二种for循环,打印出来一样是无序的。

 
[root@localhost DAMENG]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\
huluwa[4]="五娃"; for(i in huluwa){print i, huluwa[i]}}'
4 五娃
0 大娃
1 二娃
2 三娃
3 四娃
 

上文中,我们都是手动给数组中的元素赋值。那么我们能否将文本中指定的字段分割,将分割后的字段自动复制到数组中呢。答案当然是可以的,但是实现这个功能我们需要借助split函数。函数在下一章节介绍,不过需要说明的是,通过split函数生成的数组下标默认是从1开始的,这就是为什么之前说,awk数组的默认下标是从1开始的了。

实例应用

 在实际工作中,我们经常需要使用数组来统计一个字符串出现的次数。比如统计日志中每个IP出现的次数,我们可以使用数组来统计。但是,有时候我们需要使用一些特殊用法,后边再细聊。

在awk中,我们可以进行数值运算,示例如下:

[root@localhost DAMENG]# awk 'BEGIN{a=1; print a; a=a+1; print a}'
1
2
[root@localhost DAMENG]# awk 'BEGIN{a=1; print a; a++; print a}'
1
2

a的值为1 ,自加后,打印之后a的值增加1。这里a本身就是一个数字,那么如果a是字符串,能否进行自家运算呢,我们试一下:

 
[root@localhost DAMENG]# awk 'BEGIN{a="test"; print a; a++; print a; a++; print a}'
test
1
2
[root@localhost DAMENG]# awk 'BEGIN{a=""; print a; a++; print a; a++; print a}'

1
2
 

如上所示,在awk中,如果变量为字符串,也可以进行自加运算。如果字符串参与运算,字符串将呗当作数字0进行运算。空字符串一样在参与运算时,也会被当做数字0。那么如果我们引用一个不存在的元素,并对其进行自加运算的结果如何,来试一下:

[root@localhost DAMENG]# awk 'BEGIN{print arr["ip"]; arr["ip"]++; print arr["ip"]}'

1
[root@localhost DAMENG]# awk 'BEGIN{print arr["ip"]; arr["ip"]++; arr["ip"]++; print arr["ip"]}'

2

如上所示,引用一个不存在的元素时。元素被赋值为一个空字符串,当对这个元素进行自加运算,这个元素就被当成0。加一次就变成了1。利用这个特性,我们可以统计文本中某些字符出现的次数。比如IP地址,示例如下:

 
[root@localhost shell]# cat test8 
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.12
192.168.1.3
192.168.1.3
192.168.1.2
192.168.1.1
192.168.1.2
192.168.1.3
[root@localhost shell]# awk '{count[$1]++} END{for(i in count){print i, count[i]}}' test8 
192.168.1.12 1
192.168.1.1 2
192.168.1.2 3
192.168.1.3 4
 

上图中,我们使用了一个空模式,一个END模式。在空模式中,创建了一个数组,并使用数组作为元素的下标。当执行第一行时,我们引用的count("192.168.1.1")。很明显这个元素并不存在,所以当执行了自加运算后,count("192.168.1.1")被赋值为1。同理,执行第二行。直到再次遇到相同的IP地址时,使用同一个IP地址的元素会再加1。因此,我们统计出每个IP出现的次数。

如果想统计文本中每个人名出现的次数,就可以使用上述套路了:

 
[root@localhost shell]# cat test3 
Allen Phillips
Green Lee
William Aiden James Lee
Angel Jack
Tyler Kevin
Lucas Thomas
Kevin
[root@localhost shell]# awk '{for(i=1;i<=NF;i++){count[$i]++}} END{for(j in count){print j, count[j]}}' test3
Tyler 1
Angel 1
James 1
Lucas 1
William 1
Thomas 1
Green 1
Jack 1
Phillips 1
Kevin 2
Lee 2
Allen 1
Aiden 1
 

关于awk数组的用法,我们先总结这么多。这并不是数组的全部,上边这么多其实已经够用了。

awk内置函数

awk中,可以自定义函数,也有内置函数,我们今天来说一下常用的内置函数。awk的内置函数大致可以分为算数函数,字符串函数,时间函数,其他函数等,这里我们介绍一些常用的内置函数。

算数函数

最常用的算数函数有rand函数,srand函数,int函数

可以使用rand函数生成随机数,使用rand函数时,需要配合srand函数。否则rand函数返回的值将一成不变。

[root@localhost shell]# awk 'BEGIN{print rand()}'
0.237788
[root@localhost shell]# awk 'BEGIN{print rand()}'
0.237788
[root@localhost shell]# awk 'BEGIN{print rand()}'
0.237788

可以看到,如果单纯的使用rand函数,生成的值是不变的。可以配合srand函数,生成一个大于0小于1的随机数。示例如下:

[root@localhost shell]# awk 'BEGIN{srand(); print rand()}'
0.237545
[root@localhost shell]# awk 'BEGIN{srand(); print rand()}'
0.698321
[root@localhost shell]# awk 'BEGIN{srand(); print rand()}'
0.209919

可以看到,上例中生成的随机数都是小于1 的小数,如果我们想要生成随机整数。可以将上例中生成的随机数乘以100,然后截取证书部分,使用int函数可以截取整数部分的值,示例如下:

[root@localhost shell]# awk 'BEGIN{srand();print rand()}'
0.797639
[root@localhost shell]# awk 'BEGIN{srand();print 100*rand()}'
79.3658
[root@localhost shell]# awk 'BEGIN{srand();print int(100*rand())}'
87

字符串函数

我们可以使用gsub函数或者sub函数替换某些文本。

如果我们想把一个文本中的小写字母l都换成大写字母L。则可以使用gsub函数,示例如下:

 
[root@localhost shell]# cat test9 
Allen Pjillips
Green Lee
William Ken Allen
[root@localhost shell]# awk '{gsub("l","L",$1); print $0}' test9
ALLen Pjillips
Green Lee
WiLLiam Ken Allen
 

 如上所示,我们使用gsub函数将小写字母l替换成大写字母L。但是替换范围只局限于$1。所以我们看到只有第一列的小写字母l替换成了大写米姆L,其他列并没有替换。如果像替换文本中所有的小写字母l,可以把$1换成$0。或者直接省略第三个参数,省略第三个参数时默认是$0。

 
[root@localhost shell]# awk '{gsub("l","L",$0);print $0}' test9 
ALLen PjiLLips
Green Lee
WiLLiam Ken ALLen
[root@localhost shell]# awk '{gsub("l","L");print $0}' test9 
ALLen PjiLLips
Green Lee
WiLLiam Ken ALLen
 

通过上述例子,应该已经明白,gsub函数会在指定范围内查找指定的字符,并将其替换为指定字符串。

其实我们呢还可以根据正则表达式,替换字符串,示例如下:

 
[root@localhost shell]# cat test9 
Allen Pjillips
Green Lee
William Ken Allen
[root@localhost shell]# awk '{gsub("[a-z]","6",$1);print $0}' test9 
A6666 Pjillips
G6666 Lee
W666666 Ken Allen
 

我们再来看一下sub函数,sub函数和gsub函数有什么区别呢。来对比一下:

 
[root@localhost shell]# awk '{sub("l","L",$1);print $0}' test9 
ALlen Pjillips
Green Lee
WiLliam Ken Allen
[root@localhost shell]# awk '{gsub("l","L",$1);print $0}' test9 
ALLen Pjillips
Green Lee
WiLLiam Ken Allen
 

从示例可以看出,当使用gsub函数时,gsub会替换指定范围内所有符合条件的字符。当使用sub函数时,sub函数只会替换指定范围内第一次匹配到的符合条件的字符。我们可以理解gsub是指定范围内的全局替换,sub是指定范围内的单次替换。

length函数,获取指定字符串的长度。示例如下:

 
[root@localhost shell]# cat test9 
Allen Pjillips
Green Lee
William Ken Allen
[root@localhost shell]# awk '{for(i=1;i<=NF;i++){print $i, length($i)}}' test9
Allen 5
Pjillips 8
Green 5
Lee 3
William 7
Ken 3
Allen 5
 

如上所示,我们输出了每个字符串的长度。其实,length可以省略传入的参数,即不知道任何字符。当省略参数时,默认使用$0作为参数。这样我们就可以使用length函数,获取到文本每一行的长度。示例如下:

[root@localhost shell]# awk '{print $0, length()}' test9 
Allen Pjillips 14
Green Lee 9
William Ken Allen 17

index函数,获取指定字符位于整个字符串中的位置。示例如下:

 
[root@localhost shell]# cat test9 
Allen Pjillips
Green Lee
William Ken Allen
[root@localhost shell]# awk '{print index($0,"Lee")}' test9 
0
7
0
 

上例中,我们使用index函数,在每一行寻找字符串"Lee"。如果Lee存在于当前行,则返回字符串在当前行的位置。如果不存于当前行则返回0,第二行包含Lee。Lee位于第二行地七个字符的位置,所以返回7。

split函数,将指定字符串按照指定的分隔符切割。

在前边提到数组时,我们提到可以通过split函数动态生成数组,而不用手动设置数组中每个元素的值。示例如下:

[root@localhost shell]# awk -v ts="大娃:二娃:三娃" 'BEGIN{split(ts,huluwa,":");for(i in huluwa){print huluwa[i]}}'
大娃
二娃
三娃

如上所示,我们通过split函数将字符串ts切割。以 :作为分隔符,将分割后的字符串保存到名为huluwa的数组中。当我们输出数组时,每个元素的值为分割后的字符。其实,split函数本身也有返回值,其返回值就是分割以后的数组长度,示例如下:

[root@localhost shell]# awk -v ts="大娃:二娃:三娃" 'BEGIN{print split(ts,huluwa,":")}'
3

注意:被split分割后的数组的元素时从下标1开始的,而且数组中输出的元素可能与字符串中字符的顺序不同。原因上边讲数组的时候已经聊过了,如果想要按照顺序输出数组中的元素,可以使用以下方法:

 
[root@localhost shell]# awk -v ts="qq te ab th" 'BEGIN{split(ts,arr," ");for(i in arr){print arr[i]}}'
th
qq
te
ab
[root@localhost shell]# awk -v ts="qq te ab th" 'BEGIN{split(ts,arr," ");for(i=1;i<=4;i++){print arr[i]}}'
qq
te
ab
th
 

 

其他函数

 

我们还可以通过asort函数根据元素的值进行排序。但是经过asort函数排序过后数组的下标会被重置,示例如下:

 
[root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; for(i in t){print i, t[i]}}'
a 66
b 88
c 3
[root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; asort(t); for(i in t){print i, t[i]}}'
1 3
2 66
3 88
 

如上所示,数组中的值均为数字。但是下标为自定义的字符串。通过asort函数对数字进行排序后,再次输出数组中的元素。已经按照元素的值的大小进行了排序,但是数组的下标被重置为了纯数字。其实asort还有一种用法,就是对原数组进行排序时,创建一个新数组,把排序后的元素放到新的数组中。这样就可以保持原数组不呗该改变,只有打印新数组中元素的值,就可以输出排序后的元素值,示例如下:

 
[root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; asort(t,newt); for(i in t){print i, t[i]}}'
a 66
b 88
c 3
[root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; asort(t,newt); for(i in newt){print i, newt[i]}}'
1 3
2 66
3 88
 

其实,asort函数本身也有返回值。它的返回值是数组的长度:

[root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; len=asort(t,newt); for(i=1;i<=len;i++){print i, newt[i]}}'
1 3
2 66
3 88

接下来,我们看一下sorti函数。

使用asort函数可以根据元素的值进行排序,而使用asorti函数可以根据元素的下标进行排序。

当元素的下标为字符串时,可以使用asorti函数,根据下标的字符顺序进行排序。如果下标是数字,用for循环就可以排序了。当数组的下标为字符串时,asorti函数根据原数组中的下标的字母顺序进行排序,并且讲排序后的下标放到一个新的数组中。并且asorti函数会返回新数组的长度。示例如下:

 
[root@localhost shell]# awk 'BEGIN{t["z"]=66; t["q"]=88; t["a"]=3; for(i in t){print i, t[i]}}'
z 66
a 3
q 88
[root@localhost shell]# awk 'BEGIN{t["z"]=66; t["q"]=88; t["a"]=3; len=asorti(t, newt); for(i=1;i<=len;i++){print i, newt[i]}}'
1 a
2 q
3 z
 

如上所示,asorti对数组t下标进行排序后,创建一个新的数组newt。既然我们已经得到了数组t的下标,那么就可以根据下标输出数组t元素的值。从而达到对原数组下标排序后,输出原数组元素的目的。

[root@localhost shell]# awk 'BEGIN{t["z"]=66; t["q"]=88; t["a"]=3; len=asorti(t, newt); for(i=1;i<=len;i++){print i, newt[i], t[newt[i]]}}'
1 a 3
2 q 88
3 z 66

awk之三元运算和打印奇偶行

三元运算

在前边介绍if...else结构时,我们有个例子,centos7中,ID小于1000的是系统用户,大于1000的是普通用户。我们以centos7为例,用awk找出哪些是系统用户,哪些是普通用户。

[root@localhost shell]# awk -v FS=":" '{if($3<1000){usertype="系统用户"}else{usertype="普通用户"}; print $1, usertype}' passwd
root 系统用户
bin 系统用户
dmdba 普通用户

其实,我们可以通过三元运算,来替换if...else结构。示例如下:

[root@localhost shell]# awk -v FS=":" '{usertype=$3<1000?"系统用户":"普通用户"; print $1, usertype}' passwd
root 系统用户
bin 系统用户
dmdba 普通用户

如上所示,我们利用三元运算代替了if...else结构。三元运算的语法如下:

条件?结果1 :结果2

如果条件成立,则返回结果1,条件不成立返回结果2。

其实三运运算还有另外一种形式,   表达式1? 表达式2 :表达式3    示例如下:

[root@localhost shell]# awk -v FS=":" '{usertype=$3<1000? a++:b++} END{print a, b}' /etc/passwd
19 1

通过上述命令,统计出了,系统用户有19个,普通用户有1个。

打印奇偶行

我们想要使用awk打印文本中的奇数行或偶数行。是很简单的,我们先来看一下:

 
[root@localhost shell]# cat test10 
第 1 行
第 2 行
第 3 行
第 4 行
第 5 行
第 6 行
第 7 行
[root@localhost shell]# awk 'i=!i' test10 
第 1 行
第 3 行
第 5 行
第 7 行
[root@localhost shell]# awk '!(i=!i)' test10 
第 2 行
第 4 行
第 6 行
 

如上所示,我们打印了文本的奇数行和偶数行。想知道原理,需要明白以下两点

(1)、在awk中,如果省略了模式对应的动作。当前行满足模式时,默认动作为打印整行。即{print $0}

(2)、在awk中,0或空字符串表示假,非0或者非空字符串表示真

我们来详细说一下以上两点。

之前介绍过,模式可以理解为条件。如果当前行与模式匹配,则执行相应的动作。示例如下:

 
[root@localhost shell]# awk '/1/{print $0}' test10 
第 1 行
[root@localhost shell]# awk '$2>5{print $0}' test10 
第 6 行
第 7 行
[root@localhost shell]# awk '/1/' test10 
第 1 行
[root@localhost shell]# awk '$2>5' test10 
第 6 行
第 7 行
 

由上四个例子,我们发现。如果awk命令中的动作省略,会默认输出整行

注意:空模式和BEGIN/END模式除外。

第2点,在awk中,0或空字符串表示假,非0或这非空字符串表示真。怎么理解呢,模式可以理解为条件,条件成立则为真,条件不成立则为假。所以模式为真执行相应的动作,模式为假时不执行相应的动作。那么能不能直接把模式替换为真或者假呢,我们试一下:

 
[root@localhost shell]# awk '{print $0}' test1
test1   test10  test11  
[root@localhost shell]# awk '{print $0}' test1
test1   test10  test11  
[root@localhost shell]# cat test11 
abcd
[root@localhost shell]# awk '{print $0}' test11
abcd
[root@localhost shell]# awk '1{print $0}' test11
abcd
[root@localhost shell]# awk '2{print $0}' test11
abcd
[root@localhost shell]# awk '2' test11
abcd
[root@localhost shell]# awk '0{print $0}' test11
[root@localhost shell]# awk '0' test11
 

由上面几个例子,我们可以得出,只要模式为真,就执行动作,模式为假,不执行动作。其实还可以对模式取非,非真即假,非假即真。

 
[root@localhost shell]# awk '0' test11
[root@localhost shell]# awk '!0' test11
abcd
[root@localhost shell]# awk '5' test11
abcd
[root@localhost shell]# awk '!5' test11
[root@localhost shell]#
 

我们再来延伸以下

[root@localhost shell]# awk 'i=1' test11
abcd

 上例中,其实使用了awk的变量,将变量i赋值为1。当i=1以后,i为非零值,表示为真,所以上述例子输出了所有行。这时候,我们再来看打印奇数行的示例:

[root@localhost shell]# awk 'i=!i' test10
第 1 行
第 3 行
第 5 行
第 7 行

当awk处理第一行时,变量i被初始化,值为空。对空取非,所以此时可以认为模式为真。所以输出了第一行,同时取非后的值由赋予了变量i。此时i为真,当处理第二行时,对i再次取非 ,i又变成了假,所以第二行没有输出。以此类推就实现了打印奇偶行。

 

转自:https://www.cnblogs.com/jkin/p/10751394.html

 

posted @ 2020-11-07 23:51  钟桂耀  阅读(1861)  评论(0编辑  收藏  举报