zoukankan      html  css  js  c++  java
  • 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
    ", $1}' test
    aaa
    swe

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

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

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

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

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

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

    (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
    ", $1, $2}' test
    第一列:aaa   第二列:bbb
    第一列:swe   第二列:ef4

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

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

     
    [root@localhost shell]# awk -v FS=":" 'BEGIN{printf "%-10s	 %s
    ", "用户名称","用户ID"} {printf "%-10s	 %s
    ", $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
    ","用户名","用户ID"} /^s/{printf "%-15s%10s
    ", $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

  • 相关阅读:
    IE10、IE11下SCRIPT5009: “__doPostBack”未定义
    CSS Hack大全-可区分出IE6-IE10、FireFox、Chrome、Opera
    HTML head 头标签
    html5匹配不同分辨率样式
    html5关键帧动画,一个小例子快速理解关键帧动画
    导入Excel到数据库
    JavaScript树(一) 简介
    深入解析浏览器的幕后工作原理(五) 呈现树
    深入解析浏览器的幕后工作原理(四) DOM树
    深入解析浏览器的幕后工作原理(三) 呈现树和 DOM 树的关系
  • 原文地址:https://www.cnblogs.com/zhongguiyao/p/13943027.html
Copyright © 2011-2022 走看看