本文实验环境:vmware workstation 10 +Centos6.6 X86_64,文中命令请谨慎使用。

         AWK是类UNIX操作系统中一款文本处理工具,同grep,sed并成为文本处理三剑客,使用上的侧重点为:生成格式化的文本报告,但awk有丰富的语法和灵活的变量,称其为一门独立的语言也不为过。下文仅仅是awk生成格式化文本报告功能的简单使用。

        1.awk工作过程描述:

          awk将被处理的文件,逐行读入;按照输入分隔符,对改行进行分割处理;分割后的行内容块的索引从1开始,索引0为整行内容;根据定义的处理方式对行内容进行匹配,若匹配条件,则以合适的格式打印相应的行数据块,若行数据不匹配相应条件,则不处理。

        2.awk语法格式

            对awk来说很难总结出一个完全适应任何场合的语法格式,就生成格式化文本报告功能来说可以勉强写为下面的:

         $awk [option] '[PATTERN] { ACTION STATEMENT }' file

          option:

                    经常用到的有2个:

                    -F PUNCT:指定输入分隔符。

                    -v  VAL_NAME=VALUE:自定义变量值。下文有例子说明。

            其它的部分,请根据下面的例子进行学习,文末尾再进行总结。

        3.例子教学:

                3.1)打印/etc/passwd文件中root用户条目的用户名,uid和shell。     

       先取出/etc/passwd文件中root用户的条目  

[lijun@Test02 ~]$ grep  '^root\>'  /etc/passwd
root:x:0:0:root:/root:/bin/bash
#因没说到行模式匹配,故这里借用grep搜索root开头的行

       可以看到关于root用户的条目处理时使用“:”为分隔符是个明智的选择,用户名在第一部分,uid在第三部分,shll在第七部分,所以可以使用awk这样来写,就满足题意:

[lijun@Test02 ~]$ grep '^root\>' /etc/passwd | awk -F ':' '{print $1,$3,$7}'
root 0 /bin/bash
[lijun@Test02 ~]$
#因没说到行模式匹配,故这里借用grep搜索root开头的行

       上述awk表达式中,使用-F指定输入分隔符为“:”; ACTION为 print,就是打印的意思, STATEMENT是 $1,$3,$7  

当然也可这样写:

[lijun@Test02 ~]$ grep '^root\>' /etc/passwd | awk -v FS=':' '{print $1,$3,$7}'
root 0 /bin/bash
[lijun@Test02 ~]$
#因没说到行模式匹配,故这里借用grep搜索root开头的行

   总结一:ACTION: print

   print:

     格式:
            print item1,item2,...
            要点:
                 1)各item间使用逗号分隔,而输出时使用输出分隔符分隔,输出分隔符默认为空格。
                 2)输出的各item为字符串,数值和当前记录的字段($n),变量的值或awk的表达式,
                    数值会被隐式转换为字符进行输出。
                 3)print后面的item如果省略,相当于print $0;输出空白 相当于print " "


       上述2种awk表达方式可以完成题目的要求,但是输入的内容不是很美观,可以使用printf 代替print 来打印输出格式:

[lijun@Test02 ~]$ grep '^root\>' /etc/passwd | awk -F ':' '{printf "USERNAME:%-s\nUID:%-d\nSHELL:%-s\n",$1,$3,$7}'
USERNAME:root  
UID:0
SHELL:/bin/bash
[lijun@Test02 ~]$
#因没说到行模式匹配,故这里借用grep搜索root开头的行

      "USERNAME:%-s\nUID:%-d\nSHELL:%-s\n" 是格式符,因后边有3个部分要打印,故这里给出了3个格式符,USERNAME:%-s\n是针对第一个item即题目中的要打印的用户名,UID:%-d\n是针对第二个item即题目中的要打印的UID,SHELL:%-s\n针对的是第三个item即用户的shell。并且每个格式符中明确指定了换行符\n

虽然好看了,但是awk表达式也复杂起来,下面就总结下ACTION中printf的使用:

   总结二:ACTION:  printf:
         格式:
            printf  format,item1,item2,...
            要点:
                 1)printf中format称为格式符,是必须的,需明确给出
                 2)不会自动换行,需显示指定换行符\n
                 3)format中需要分别为后面每一个item指定一个格式符
                
                 格式符:都以%开头,后跟一个字符
                     %c:显示字符ASCII码,其实显示字符本身
                     %d %i:显示十进制格式的整数
                     %e %E:科学计数法显示数值
                     %f:显示浮点数
                     %g %G:以科学计数法格式或浮点数格式显示数值
                     %s:显示字符串
                     %u:显示无符号整数
                     %%:显示%自身
                 格式的修饰符:
                     #[.#]:第一个#为数字,显示宽度,后边.#在表达浮点数时表示精度              
                     -:左对齐,默认右对齐          
                     +:显示数值的符号, 正负数


            3.2)使用"#"为连接符,显示系统上root用户的名称,uid和shell的设定

[root@Test02 ~]# grep '^root\>' /etc/passwd | awk -v FS=':' -v OFS='#' '{print $1,$3,$7}'
root#0#/bin/bash
[root@Test02 ~]#
#因没说到行模式匹配,故这里借用grep搜索root开头的行

     总结三:awk变量:FS为行输入分隔符,OFS为行输出分隔符,使用-v val_name=value来定义,-F PUNCT也是指定输入分隔符的,二者默认为空格。

            3.3)打印/etc/passwd文件中用户名为一行

    因awk是逐行对文件进行处理的,故/etc/passwd文件中每行的$1就是用户名,但是对输出换行符不进行指定的话,打印出来是这个样子:

[root@Test02 ~]# head -3 /etc/passwd | awk -F ':' '{print $1}'
root
bin
daemon
[root@Test02 ~]#

    因此需要指定输出换行符,这样才可将/etc/passwd文件中用户名打印为一行

[root@Test02 ~]# awk -v FS=':' -v ORS=' ' '{print $1}' /etc/passwd
root bin daemon adm lp sync shutdown halt mail uucp operator games gopher ftp nobody dbus usbmuxd vcsa rpc rtkit avahi-autoipd abrt rpcuser nfsnobody haldaemon gdm ntp apache saslauth postfix pulse sshd tcpdump lijun [root@Test02 ~]#

总结三:awk变量:RS为awk输入换行符,ORS为输出换行符,使用-v val_name=value来定义

            3.4)显示/etc/passwd中第一行有几个字段,并打印最后那个字段

[root@Test02 ~]# head -1 /etc/passwd | awk -F ':' '{print NF,$NF}'
7 /bin/bash

总结三:awk变量:NF为当前行可被分割的成几个字段,$NF为被分割后最后那个字段,需指明行分隔符,默认为空格。

            3.5)打印显示/etc/issue和/etc/sysconfig/network的行和行号,仔细观察下面三段显示的不同

方式一:
[root@Test02 ~]# awk '{print NR,$0}' /etc/issue /etc/sysconfig/network
1 CentOS release 6.6 (Final)
2 Kernel \r on an \m
3 
4 NETWORKING=yes
5 HOSTNAME=Test02.lijun.com
[root@Test02 ~]#
方式二:
[root@Test02 ~]# awk '{print FNR,$0}' /etc/issue /etc/sysconfig/network
1 CentOS release 6.6 (Final)
2 Kernel \r on an \m
3 
1 NETWORKING=yes
2 HOSTNAME=Test02.lijun.com
[root@Test02 ~]# awk '{print FILENAME,FNR,$0}' /etc/issue /etc/sysconfig/network
/etc/issue 1 CentOS release 6.6 (Final)
/etc/issue 2 Kernel \r on an \m
/etc/issue 3 
/etc/sysconfig/network 1 NETWORKING=yes
/etc/sysconfig/network 2 HOSTNAME=Test02.lijun.com
[root@Test02 ~]#

总结三:awk变量:一行命令中同时处理多个文件时,NR:后边要处理的文件的行进行统一计数,显示当前行的行号。FNR:后边每个要处理的文件的行数单独计数时,显示当前行的行号。FILENAME:当前被处理的文件的文件名。

            3.6)

(1)打印显示/etc/passwd文件中普通用户的信息

在centos6.6上系统中的匹配用户的uid从500开始的,故这里只需打印/etc/passwd文件中uid列大于等于500的用户行即可

[root@Test02 ~]# awk -v FS=':' '$3>=500{print $0}' /etc/passwd
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
lijun:x:500:500:lijun:/home/lijun:/bin/bash
[root@Test02 ~]#

这里用到可行模式匹配,即awk语法格式中的pattern的定义。

总结四:PATTERN:

    关系表达式,即上例中的'$3>=500

    涉及到比较操作符: >,>=,< ,<=,==,!=

(2)使用ifconfig eth1显示eth1网卡的配置信息,抓取ip地址所在的行

[root@Test02 ~]# ifconfig eth1
eth1      Link encap:Ethernet  HWaddr 00:0C:29:70:8F:0B  
          inet addr:192.168.100.2  Bcast:192.168.100.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe70:8f0b/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:3806 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1764 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:350119 (341.9 KiB)  TX bytes:189329 (184.8 KiB)
[root@Test02 ~]#

因IP地址所在的行在第二行,故借助NR可以抓取出来。

[root@Test02 ~]# ifconfig eth1|awk 'NR==2{print $0}'
          inet addr:192.168.100.2  Bcast:192.168.100.255  Mask:255.255.255.0
[root@Test02 ~]#

            3.7)显示/etc/passwd文件中root用户行信息

因在/etc/passwd文件每一行开头的都是用户名,故抓取root开头且为独立单词的行即可,正则表达式为^root\>

[root@Test02 ~]# awk -F ':' '/^root\>/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@Test02 ~]#

总结四:PATTERN:

        /regular expression/:仅处理能被/regular expression/匹配的行

上述例子中root若不加词尾锚钉,会出现下列现象:

[root@Test02 ~]# awk -F ':' '/^root/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
rootli:x:501:501::/home/rootli:/bin/bash

            3.8)显示/etc/passwd文件中root开头的行至shutdown开头的行之间的内容

[root@Test02 ~]# awk -F':' '/^root/,/^shutdown/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
rootli:x:501:501::/home/rootli:/bin/bash
[root@Test02 ~]#

总结四:PATTERN:

        line rangs:行范围,类似于sed 或vim 中的定界符,startline,endline

            3.9)打印系统上shell为bash的用户,开始和结束要有明确提示:

[root@Test02 ~]# awk -F: 'BEGIN{print "======================"}BEGIN{printf "%-15s%-s\n","username","shell"}$NF~/bash$/{printf "%-15s%-s\n",$1,$NF}END{print "======================"}' /etc/passwd
======================
username       shell
root           /bin/bash
lijun          /bin/bash
rootli         /bin/bash
======================
[root@Test02 ~]#

总结四:PATTERN:

        BEGIN:同要处理的文件内容没关系,只是在所有awk动作之前执行一个动作
        END:在awk所有的动作结束后,执行的动作

            3.10)显示系统中的用户uid在0与100之间的用户

因用户的UID在/etc/passwd文件中第3部分,因此只要对/etc/passwd文件使用":"为输入行分隔符,抓取第三部分进行条件过滤即可实现

[root@Test02 ~]# awk -F ':' '{if($3>0)if($3<100)printf "%-12s%-d\n",$1,$3}' /etc/passwd
bin         1
daemon      2
adm         3
lp          4
sync        5
shutdown    6
halt        7
mail        8
uucp        10
operator    11
games       12
gopher      13
ftp         14
nobody      99
dbus        81
vcsa        69
rpc         32
rpcuser     29
haldaemon   68
gdm         42
ntp         38
apache      48
postfix     89
sshd        74
tcpdump     72
[root@Test02 ~]#

总结五:awk中的控制语句if;

    格式:

        if(condition){statement1;statement2;...}[else {statement1;statement2;...}]
             当 statement只有一个时,{}可以被省略
             单分支if语句时 {}可省略

        上面的例子中if语句相当于2个if语句的嵌套,若写成bash脚本格式如下:

        if [ $3 -gt 0 ];then

                if [ $3 -lt 100 ];then

                        echo .....

                fi

         fi

        awk中是不是不用写关闭符很爽,不用写bash脚本那样死板的格式很爽,那就去玩玩python吧,python的脚本写着同样很爽,至少比bash 爽。

            3.11)显示/etc/inittab文件第一句中每个字段的长度

[root@Test02 ~]# head -1 /etc/inittab | awk '{i=1;while(i<=NF){print $i,length($i);i++}}'
# 1
inittab 7
is 2
only 4
used 4
by 2
upstart 7
for 3
the 3
default 7
runlevel. 9
[root@Test02 ~]#
#length($i)是awk的内置函数,用来显示字符串的长度。

总结五:awk中的控制语句:while

    格式:
         while(condition){statment1;statment2;...}
            条件为真时循环,直到为假时退出;
            通常用于在当前行的各字段间进行循环

      在上面的awk语句中定义了变量i,NF为awk内置变量表示当前行可被分割成的字段数,当变量i<=NF为真时,while循环启动,显示$i为index的字段,length($i)为字段的长度,让后用i++自加,指导i<=NF为假时,停止while循环,停止打印。

 上面的例子使用另外一种格式来实现:

[root@Test02 ~]# head -1 /etc/inittab | awk '{for(i=1;i<=NF;i++)print $i,length($i)}'
# 1
inittab 7
is 2
only 4
used 4
by 2
upstart 7
for 3
the 3
default 7
runlevel. 9
[root@Test02 ~]#

总结五:awk中的控制语句for

    格式:

        for:
         for(expr1;expr2;expr3) statement
           expr1:变量初始值
           expr2:条件
           expr3:变量变化

            3.12)打印/etc/passwd中uid为奇数的用户条目

[root@Test02 ~]# awk -F':' '{if($3%2 !=0)print $0}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
halt:x:7:0:halt:/sbin:/sbin/halt
operator:x:11:0:operator:/root:/sbin/nologin
gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
usbmuxd:x:113:113:usbmuxd user:/:/sbin/nologin
vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin
rtkit:x:499:497:RealtimeKit:/proc:/sbin/nologin
abrt:x:173:173::/etc/abrt:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
pulse:x:497:496:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
rootli:x:501:501::/home/rootli:/bin/bash
[root@Test02 ~]#

另外一种写法:

[root@Test02 ~]# awk -F':' '{if($3%2==0)next;print $0}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
halt:x:7:0:halt:/sbin:/sbin/halt
operator:x:11:0:operator:/root:/sbin/nologin
gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
usbmuxd:x:113:113:usbmuxd user:/:/sbin/nologin
vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin
rtkit:x:499:497:RealtimeKit:/proc:/sbin/nologin
abrt:x:173:173::/etc/abrt:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
pulse:x:497:496:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
rootli:x:501:501::/home/rootli:/bin/bash
[root@Test02 ~]#

总结五:awk中的控制语句,中特殊关键字:

    next:提前结束对本行的处理,而进入下一行的处理

    break:跳出当前循环

    continue:提前结束本轮循环,进入下一轮循环

    在上面的例子中使用$3%2:$3对2取模,就是求余数,若余数为0,则被除数$3为偶数,若余数不为0则被除数$3为奇数。


            3.13)统计当前OS的tcp连接状态和状态的个数

显示os的tcp连接状态:

[root@Test02 ~]# ss -tan
State       Recv-Q Send-Q                            Local Address:Port                              Peer Address:Port 
LISTEN      0      128                                          :::22                                          :::*     
LISTEN      0      128                                           *:22                                           *:*     
LISTEN      0      128                                   127.0.0.1:631                                          *:*     
LISTEN      0      128                                         ::1:631                                         :::*     
LISTEN      0      128                                           *:60791                                        *:*     
LISTEN      0      100                                         ::1:25                                          :::*     
LISTEN      0      100                                   127.0.0.1:25                                           *:*     
LISTEN      0      5                                             *:5901                                         *:*     
LISTEN      0      128                                          :::111                                         :::*     
LISTEN      0      128                                           *:111                                          *:*     
LISTEN      0      128                                          :::45680                                       :::*     
ESTAB       0      0                                 192.168.100.2:22                             192.168.100.100:49280 
[root@Test02 ~]#

可以看到左边第一列就是tcp的连接状态,但是第一行要去掉。

[root@Test02 ~]# ss -tan|awk '!/^State/{connect[$1]++}END{for(i in connect)print i,connect[i]}'
ESTAB 1
LISTEN 11
[root@Test02 ~]#

总结六:awk的数组:

      格式:array_name[index-expression]

          awk中的数组是关联数组,有的编程语言如python中会称关联数组为字典。
          index-expression:可以使用任意字符串;             
          如果某数组元素不存在,在引用时,awk会自动创建该元素并将其初始化为空串
          在数组中遍历每一个元素:使用 for(var in array){for body}
                   var会遍历array的每一个索引,print array[var]显示该索引代表的值

      上面的例子中使用/^State/,这个正则表达式匹配#ss -tan的第一行,使用!/^State/ 表示对这个模式取反,即要处理不以State开头的行。定义了关联数组connect[],使用$1为数组的index值,处理每一行时,若connect[$1]存在则数字加1,若connect[$1]不存在则创建。awk动作结束后,使用for语句来处理,上例中i代表connect数组的index,connect[i]则为该index对应的值。


总结七:awk的内置函数处理字符串时使用:
         字符串处理:

     length([s]):返回指定字符串的长度
     sub(r,s[,t]):以r所代表的模式来查找t字符串中的匹配,将其第一次出现替换同s所表示的字符串
                   #awk -F:'{sub(root,ROOT,$0)}' /etc/passwd

     gsub(r,s[,t]):以r所代表的模式来查找t字符串中的匹配,将所有出现替换为s所表示的字符串
     split(s,a[,r]):以r为分隔符切割字符串s,并将切割的结果保存至数组a中,
            len=split(s,a[,r])为被切割后的段数

#netstat -tan|awk '/^tcp/{len=split($5,count,":");ip[count[len-1]]++}END{for(i in ip){print i,ip[i]}}'

             <统计连接中,Foreign Address出现的次数>        
     substr(s,i[,n]):从s表示的字符串中取子串,从i开始,取n个字符

    4 awk实际应用举例:

4.1)取出当前网卡eth1的ipv4地址:

eth1地址格式:

[root@Test02 ~]# ifconfig eth1
eth1      Link encap:Ethernet  HWaddr 00:0C:29:70:8F:0B  
          inet addr:192.168.100.2  Bcast:192.168.100.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe70:8f0b/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14759 errors:0 dropped:0 overruns:0 frame:0
          TX packets:13311 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:1331116 (1.2 MiB)  TX bytes:7782755 (7.4 MiB)
[root@Test02 ~]#

处理方式:

[root@Test02 ~]# ifconfig eth1 | awk -F '[ :]+' 'NR==2{print $4}'
192.168.100.2
[root@Test02 ~]#

说明:-F '[ :]+' 指定输入行分隔符为 空格和':',+表示出现的次数至少1次,

               inet    addr     :   192.168.100.2    。。。。

第一列   第二列   第三列          第四列          忽略

      NR表示当前处理的行在文件中的位置行号。

      若还是不明白,自己动手试试or画个圈圈诅咒自己的智商吧!


4.2)统计下面apache access日志中给出的ip地址和访问次数

[lijun@Test02 ~]$ cat  3.txt
10.0.0.41 - -[03/Dec/2010:23:27:01 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.43 - -[03/Dec/2010:23:27:01 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.42 - -[03/Dec/2010:23:27:01 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.46 - -[03/Dec/2010:23:27:02 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.42 - -[03/Dec/2010:23:27:02 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.47 - -[03/Dec/2010:23:27:02 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.41 - -[03/Dec/2010:23:27:02 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.47 - -[03/Dec/2010:23:27:02 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.41 - -[03/Dec/2010:23:27:03 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -
10.0.0.46 - -[03/Dec/2010:23:27:03 +0800] "HEAD /checkstatus.jsp HTTP/1.0" 200 -

处理方式:

[lijun@Test02 ~]$ awk '{S[$1]++}END{for (a in S)print a,S[a]}' 3.txt | sort -rn -k2
10.0.0.41 3
10.0.0.47 2
10.0.0.46 2
10.0.0.42 2
10.0.0.43 1

    这个就不用说了吧,只是awk管理数组和sort的联用,简单的有点可耻。


4.3)打印当前系统上,磁盘分区使用超过10%的分区的分区名,可使用空间和挂载点:

[root@Test02 ~]# df -hP | awk '$1!~/^File/{if($5>="10%")printf "%-10s%-6s%-s\n",$1,$4,$6}'
/dev/sda2 14G   /
/dev/sda1 153M  /boot
[root@Test02 ~]#