文本处理工具之AWK

概述:

    在之前的文章中,我们介绍过文本处理三剑客的grep、sed,本篇就简要说明下awk的用法。主要是围绕常见的一些用法进行展开,分为以下几个部分:

    1、awk的基础语法

    2、awk的进阶语法

    3、awk的实际效果演示

第一章    awk的基础语法

    1、awk的工作原理

        每次读取一行,按照指定字符分隔,然后按照指定动作处理

        第一步:执行BEGIN{action;… }语句块中的语句

        第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{action;… }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。
        第三步:当读至输入流末尾时,执行END{action;…}语句块

        BEGIN语句块在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中
        END语句块在awk从输入流中读取完所有的行之后即被执行,比如,打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块
        pattern语句块中的通用命令是最重要的部分,也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行, awk读取的每一行都会执行该语句块

        

        如:BEGIN:所有行执行动作前的准备工作

            awk -F: 'BEGIN{print "Username:\n———-"}$3>=500{print $1}' /etc/passwd

                输出结果为:

                    Username:

                    ———-

                    nwc

            END:所有行处理完成后,最后执行的收尾工作

            awk -F: 'BEGIN{print "Username:\n———-"}$3>=500{print $1}END{print "———-"}' /etc/passwd

            输出结果为:

                Username:

                ———-

                nwc

                ———-

    2、awk的基本用法:

        awk [OPTIONS] 'SCRIPT' FILE…

        awk [OPTIONS] '/PATTERN/{ACTION}' FILE…

            awk 默认分隔符为空格,连续的多个空格会被当做一个空格,但是cut指定空格为分隔符时,只会认为单个空格为分隔

        awk [options] -f programfile var=value file…

        常用选项有:

           -F  指定字段分隔符

                可以指定多个分隔符,利用+号表示,例如:

                ifconfig eth0|grep "inet addr"|awk -F "[ :]+" '{print $4,$6,$8}',指定空格和:为分隔符

           -f /PATH/TO/FILE   指明awk处理的程序段的处理语句所在的文件路径(也就是说awk支持将程序段的语句单独写在某个文件中,然后用-f进行指明即可)

           -v VAR=VALUE  定义变量(变量也可以直接在动作内部直接定义,不用事先声明)          

                例如:

                    awk -v num1=30 -v num2=20 ‘BEGIN{print num1+num2}’

                        结果是50

                    awk ‘BENGIN{num1=20;num2=30;print num1+num2}’

               

                   

    3、awk常用的4种分隔符:

        输入:

            行分隔符:默认是\n也就是$符

            字段分隔符:默认是空格

        输出:

            行分隔符:默认是$

            字段分隔符:默认是空格

            如:awk -F : '/^root\>/{print "Usernam:"$1,"\nShell:"$7}' /etc/passwd

                输出为:Username:root

                        Shell:/bin/bash

            如:awk -F : '/^root\>/{print "Usernam:",$1,"\nShell:",$7}' /etc/passwd

                输出为:Username:  root

                        Shell:  /bin/bash

            如:awk -F : '/^root\>/{print "Usernam:",$1,"Shell:",$7}' /etc/passwd

                输出为:Username:  root  Shell:  /bin/bash

        

                 可以在action动作内部定义变量:

                    awk  'BEGIN{FS=":";OFS="–"}$3>=500{print $1}' /etc/passwd

                        这样也实现了指定分隔符为:冒号

      

    4、awk的program段中pattern详解

        <1>空模式:匹配每一行

        <2>/regular expression/:仅能处理被此处的模式匹配到的行

        <3>relational expression:关系表达式,结果有“真”有“假”;结果为“真”才会被处理

            真:结果为非0值,非空字符串

        <4>地址定界:/PAT1/,/PAT2/  从匹配到PAT1开始,到第一次匹配到PAT2结束,之间所有的行

            不支持直接给出行号,如果要表示行号范围,可使用NR>=10&&NR<=20方式

            /PAT/:被PAT匹配到的所有的行

            !/PAT/:被PAT匹配到的行之外的所有行

        关系表达式(expression):表达式,如大于,小于,等于之类的,支持的符号有:

            >:大于

            <:小于

            >=:大于等于

            <=:小于等于

            ==:等于

            !=:不等于

            ~:模式匹配,后面模式要用/PATTERN/这种表达式

        例如:

            awk -F: '$3 >= 500 && $3 <= 1000{print $1}' /etc/passwd

            awk -F: '$7=="/bin/bash"{print $1}' /etc/passwd

                也可写成:

                awk -F: '$7~/bash$/{print $1}' /etc/passwd

    5、awk的program段中action详解   

        默认为print,一个action内部有多条语句时,每个语句用;分号分隔

        <1>print

            print格式: print item1, item2, …
            要点:
                (1) 逗号分隔符
                (2) 输出的各item可以字符串,也可以是数值;当前记录的字段、变量或awk的表达式
                (3) 如省略item,相当于print $0
            示例:

                awk '{print "hello,awk"}'
                awk –F: '{print}' /etc/passwd
                awk –F: ‘{print “wang”}’ /etc/passwd
                awk –F: ‘{print $1}’ /etc/passwd
                awk –F: ‘{print $0}’ /etc/passwd
                awk –F: ‘{print $1”\t”$3}’ /etc/passwd
                tail –3 /etc/fstab |awk ‘{print $2,$4}

                

        <2>printf

            格式化输出: printf “ FORMAT” , item1, item2, …

            要点:             

                1)printf与print的最大不同是,printf需要指定格式

                2)format用于指定后面的每个item的输出格式

                3)printf语句不会自动打印换行符 \n

                4)整个format要用双引号“”引起来

                5)在format里面定义的格式的个数,要与后面的item个数匹配

            format格式的指示符都以%开头,后跟一个字符,常见的指示符如下:

                %c  显示字符的ASCII码

                %d,%i  显示为十进制整数

                %e,%E  科学计数法显示数值

                %f  显示浮点数

                %g,%G  以科学计数法的格式或浮点数的格式显示数值

                %s  显示字符串

                %u  无符号整数

                %%  显示%自身

            format修饰符:

                N  显示宽度

                –  左对齐

                +  显示数值符号

                如:%-15s

            例如:awk -F: ‘{printf “%-15s %i\n”,$1,$3}’ /etc/passwd

            在print和printf中也支持输出重定向和管道

                print items > output_file

                print items >> output_file

                print items | command

            例如:awk -F :‘{printf “%-15s %i\n”,$1,$3 > “/dev/stderr”}’  /etc/passwd

    6、awk的内置变量:

        FS 读入行时使用的字段分隔符,默认为空白

            指定输入分隔符:以指定:为例

                OPTION段定义-F :

                语句里面指定FS=":"

                        awk  'BEGIN{FS=":"}$3>=500{print $1}' /etc/passwd

                OPTION段定义 -v FS=":"

        OFS  输出时指定字段分隔符,默认为空白

            awk  'BEGIN{FS=":";OFS="–"}$3>=500{print $1}' /etc/passwd

        RS  输入时使用的换行符

        ORS  输出时使用的换行符

        NF  当前行的字段个数

        NR  文件的总行数,如果有多个文件,则是多个文件总行数之和

        FNR  当前文件中正被处理的条目在文件中是第几行,当有多个文件时,每个文件单独计数

        ARGV  数组,保存命令本身这个字符串,如:awk ‘{print $0}’ a.txt  b.txt这个命令中,ARGV[0]保存awk 、ARGV[1]保存a.txt、ARGV[2]保存b.txt

        ARGC  awk命令的参数的个数

            awk ‘BEGIN{print ARGC}’ a.txt  b.txt结果是3

        FILENAME   awk命令说出你的文件的名称

        ENVIRON  当前shell环境变量及其值的关联数组 

        $0  表示一整行

        $1,$2。。。表示第几个字段

        length($1)  获取第一个字段的字符串长度

第二章    awk的进阶语法

    awk的顺序、选择、循环,因为awk默认会自动的遍历文件中的每一行,故awk选择,循环的对象一般都是被分隔开来的各个字段

    1、awk中的操作符:

        算数操作符:

            -x  负值

            +x  转换为数值

            x^y   x的y次方

            x**y  x的y次方

            x*y  乘法

            x/y  除法

            x+y  加法

            x-y  减法

            x%y  取模,余数

        赋值操作符:

            =

            +=

            -=

            *=

            /=

            %=

            ^=

            **=

            ++

            —

            注意:如果某模式为=号,此时用/=/可能有语法错误,应该以/[=]/替代

        比较操作符:

            x < y  x小于y为真,否则为假

            x <= y  x小于等于y为真,否则为假

            x > y  x大于y为真,否则为假

            x >= y  x大于等于y为真,否则为假

            x == y  x等于y为真,不等于为假

            x != y  x不等于y为真,等于为假

            x ~ y  x模式匹配y为真,不匹配为假

            x !~ y  x模式不匹配y为真,匹配为假

            AA in array  某对象在指定的属组里面为真,不在则为假

        布尔值:

            在awk中,0为假,任何非0值都为真(与bash中相反)

        表达式之间的逻辑关系:

            &&

            ||

    2、三元条件表达式:

        条件?为真表达式:为假表达式

            相当于shell中的:

                if 条件;then

                    为真表达式

                else

                    为假表达式

                fi

        例如:

            a=5

            b=8

            a>b?a is max:b is max

        例如:

            awk -v num1=20 -v num=30 ‘BEGIN{num1>num2?max=num1:max=num2;print max}’

    3、awk中action段的if-else语句

        语法:if (条件){为真表达式} else {为假表达式}

 例如:
            awk '{if($3==0){print $1,"admin";}else{print $1,"user"}}' /etc/passwd
            或:
            awk -F: ‘{if($1=="root")print $1,"admin";else print $1,"user"}’ /etc/passwd
            awk -F: ‘{if($1=="root")printf "%-15:%s\n",$1,"admin";else printf “%-15s:%s\n”,user}’ /etc/passwd
            awk -F: -v sum=0 ‘{if($3>=500)sum++}END{print sum}’ /etc/passwd
            awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
            awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
            awk  '{if(NF>5) print $0}' /etc/fstab
            awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else{printf "root or Sysuser: %s\n",$1}}' /etc/passwd
            awk -F: '{if($3>=1000) printf "Common user: %s\n",$1;else printf "root or Sysuser: %s\n",$1}' /etc/passwd
            df -h|awk -F% '/^\/dev/{print $1}'|awk '$NF>=80{print $1,$5}’
            awk 'BEGIN{ test=100;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'

       

    4、awk中action段的while语句

        语法:while (条件){语句1;语句2;…}

 例如:
            awk -F : ‘{i=1;while(i<=3){print $i;i++}}’ /etc/passwd
            awk -F : ‘{i=1;while (i<NF){if (lenth($i)>=4) {print $i};i++}}’ /etc/passwd
            awk ‘{i=1;while(i<NF){if($i>=100) print $i;i++}}’ hello.txt
                hello.txt文件的内容为一堆随机数 
            awk '/^[[:space:]]*linux16/{i=1;while(i<=NF){print $i,length($i); i++}}' /etc/grub2.cfg
            awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=10){print $i,length($i)}; i++}}' /etc/grub2.cfg

       

    5、awk中action段中的do-while语句:至少执行一次循环体,不管条件是否满足

        语法:do {语句1,语句2,…} while (condition)

 例如:
            awk -F: ‘{i=1;do{print $i;i++}while(i<=3)}’ /etc/passwd
            awk -F: ‘{i=4;do{print $i;i--}while(i>4)}’ /etc/passwd

    6、awk中action段中的for语句

        语法:for(i=1;i<=5;i++){语句1,语句2,…}

例如:
            awk -F:‘{for(i=1;i<=3;i++)print $i}’ /etc/passwd
            awk -F: ‘{for(i=1;i<=NF;i++){if(length($i)>=4){print $i}}}’ /etc/passwd

        

        for循环还可以用来遍历数组元素,awk数组类似是关联数组,也就是下标索引不是数字,而是字符串

        语法:for(i in array){语句1,语句2,…}

            awk -F:‘{$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}’ /etc/passwd

            单独看这部分awk -F:‘{$NF!~/^$/{BASH[$NF]++}的内容的意义是:

                当/etc/passwd中最后一个字段的值不为空时,将最后一个字段的值作为BASH数组的下标,然后对应BASH[$NF]的值+1,这样就形成了数组中的元素:

                BASH[/bin/bash]  值为一共有多少个对应shell的个数

                最终就相当于统计出来了有多少种shell然后每种shell有多少个用户使用

                A in BASH在awk中遍历的是数组的下标,而不是具体元素的值

    7、awk中action段中case语句

        语法:switch(表达式){case VALUE or /REGEXP/:语句1,语句2,…default:语句1,语句2,…}

    8、awk中action段中的break、continue、next语句

        break和continue:常用语循环或case语句中

        next语句:

            提前结束对本行文本的处理,并接着处理下一行

            例如:

                awk -F:‘{if($3%2==0)next;print $1,$3}’ /etc/passwd

                作用是显示ID号为奇数的用户

        awk的循环中,基本都是对字段进行循环,continue和break都只是进行下个字段,或者跳出本轮字段的循环,但是有些时候,需要跳出,进行下一行的循环,就需要用到next

    9、awk中使用数组

        array[index_expression]

            index_expression可以使用任意字符串;需要注意的是,如果某数组元素时间不存在,那么在引用其时,awk会自动创建此元素并初始化为空串,因此要判断某数组中是否存储在元素,需要使用inde in array的方式

        awk中使用的是关联数组,也就是索引下标为字符串,当定义该数组和引用该数组时,索引下标都需要用“”双引号引起来

        关联数组中的数据不是顺序存放的,要遍历数组,需要遍历数组索引下标来引用

        要遍历数组中的每一个元素,需要使用如下的特殊结构:

            for(VAR in array){语句1,…}

            其中VAR 用于引用数组下标,而不是元素值

            array[VAR]  数组中某一元素的值

 如:
     netstat -tan | awk ‘/^tcp/{++S[$NF]}END{for(a in S)print a,S[a]}’
    作用是:每出现一个被/^tcp/模式匹配到的行,数组S[$NF]就加1,NF为当前匹配到的行的最后一个字段,
            此处用其值作为数组S的元素索引下标

    awk ‘{counts[$1]++};END{for(url in counts)print counts[url],url}’ /var/log/httpd/access_log
    用法与上一个例子一样,用于统计某日志文件中IP地址的访问量

           

        删除数组变量

            从关联数组中删除数组索引需要使用delete命令,其格式为:delete array[index]

    10、awk中的内置函数:

        <1>split(string,array [fieldsep])

            功能是:将string表示的字符串以fieldsep为分隔符进行分割,并将分割后的结果保存至array为名的数组中,数组下表为从1开始的序列:

 例如:
 netstat -tan | awk ‘/:80\>/{split($5,clients,":");IP[clients[1]]++}END{for(i in IP){print IP[i],i}}’|sort -rn|head -50
功能是显示每个客户端IP,及其请求的连接个数

netstat -tan | awk ‘/:80\>/{split($5,clients,":");ip[clients[4]]++}END{for(a in ip){print ip[a],a}}’|sort -rn|head -50
df -lh|awk '!/^File/{split($5,percnt,"%");if(percent[1]>=20){print $1}}'
显示当前系统上磁盘空间占用率超过20%的分区

            

        <2>length([string])

            功能是:返回string字符串中字符的个数

        <3>substr(string,start [,length])

            功能是:取string字符串中的子串,从start开始,取length个,start从1开始计数

        <4>sub(r,s,[t]):以r表示的模式来查找t所表示的字符串中的匹配的内容,并将其第一次出现替换为s所表示的字符串

        <5>gsub(r,s,[t]):以r表示的模式来查找t所表示的字符串中的匹配的内容,并将其所有出现的位置均替换为s所表示的字符串

        <6>system("command")

            功能是:执行系统command命令,并将结果返回至awk命令

        <7>systime()    功能是:取系统当前时间

        <8>tolower(s)   功能是:将s中的所有字母转为小写

        <9>toupper(s)   功能是:将s中的所有字母转为大写

        <10>rand()  返回0和1之间的一个随机数

            awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }'

    11、补充:time命令测试命令执行消耗时间,判断命令执行效率

time (awk 'BEGIN{total=0;for(i=0;i<=10000;i++){total+=i;};print total;}')        
time (total=0;for i in {1..10000};do total=$(($total+i));done;echo $total)        
time (for ((i=0;i<=10000;i++));do let total+=i;done;echo $total)

第三章    awk的实际效果演示

    1、显示GID小于500的组

[root@web ~]# awk -F: '{if($3>=500)printf "GroupName: %-10s GID: %-5i \n",$1,$3}' /etc/group
GroupName: nfsnobody  GID: 65534 
GroupName: nnn        GID: 500   
GroupName: natasha    GID: 506   
GroupName: harry      GID: 507   
GroupName: sarah      GID: 508   
GroupName: mysql      GID: 509   
GroupName: sysadmins  GID: 4331  
GroupName: g1         GID: 4333  
GroupName: nwc        GID: 510   
GroupName: bash       GID: 511   
GroupName: testbash   GID: 512   
GroupName: basher     GID: 513   
GroupName: nologin    GID: 514   
GroupName: gdm        GID: 515   
GroupName: testfind   GID: 516   
GroupName: sales      GID: 4336  
GroupName: mw         GID: 3001

   

    2、显示默认shell为nologin的用户

[root@web ~]# awk -F: '/\/nologin$/{print $1"====="$7}' /etc/passwd
bin=====/sbin/nologin
daemon=====/sbin/nologin
adm=====/sbin/nologin
lp=====/sbin/nologin
mail=====/sbin/nologin
uucp=====/sbin/nologin
operator=====/sbin/nologin
games=====/sbin/nologin
gopher=====/sbin/nologin
ftp=====/sbin/nologin

====================================================
 awk -F: '$7~/nologin$/{print $1}' /etc/passwd
 awk -v FS=":" '/nologin$/{print $1}' /etc/passwd

       

    3、显示eth0网卡文件的配置信息,注意只显示等号后面的值

[root@web ~]# awk -F "=" '{print $2}' /etc/sysconfig/network-scripts/ifcfg-eth0 
eth0
10.1.32.68
255.255.0.0
10.1.0.1

    

    4、显示/etc/sysctl.conf文件中定义的内核参数,只显示名称

[root@web ~]# awk -F "=" '/^[^#]/{print $1}' /etc/sysctl.conf 
net.ipv4.ip_forward 
net.ipv4.conf.default.rp_filter 
net.ipv4.conf.default.accept_source_route 
kernel.sysrq 
kernel.core_uses_pid 
net.ipv4.tcp_syncookies 
kernel.msgmnb 
kernel.msgmax 
kernel.shmmax 
kernel.shmall 
[root@web ~]#

       

    5、显示eth0网卡的ip地址,通过ifconfig的命令结果进行过滤

[root@web ~]# ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 00:0C:29:AA:19:1B  
          inet addr:10.1.32.68  Bcast:10.1.255.255  Mask:255.255.0.0
          inet6 addr: fe80::20c:29ff:feaa:191b/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:388706 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3755 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:45528070 (43.4 MiB)  TX bytes:396651 (387.3 KiB)

[root@web ~]# ifconfig eth0|awk -F "[ :]+" '/inet addr/{print $4}'
10.1.32.68
[root@web ~]#

        

    6、利用awk打印奇数行、偶数行

[root@web ~]# cat testfile1 
1
2
3
4
5
6
7
8
9
10
[root@web ~]# awk 'a=!a{print $0}' testfile1 
1
3
5
7
9
[root@web ~]# awk '!(a=!a){print $0}' testfile1 
2
4
6
8
10
[root@web ~]# awk '{if(NR%2==0)print $0}' testfile1 
2
4
6
8
10
[root@web ~]# awk '{if(NR%2==1)print $0}' testfile1 
1
3
5
7
9
[root@web ~]#

    7、统计ps aux命令执行结果中,系统上各状态的进程的个数:

[root@web ~]# ps aux | awk '/^[^USER]/{stat[$8]++}END{for(i in stat){print i,stat[i]}}'
S< 2
S<sl 1
Ss 18
SN 2
S 110
Ss+ 7
Ssl 2
R+ 1
S+ 1
Sl 1
S<s 1
[root@web ~]#

    8、统计ps aux执行结果中,当前系统上各用户的进程的个数

[root@localhost lib]# ps aux|awk 'NR>1{COUNT[$1]++}END{for(i in COUNT){print i,COUNT[i]}}'
colord 1
chrony 1
rtkit 1
polkitd 1
dbus 1
nobody 1
gdm 3
libstor+ 1
avahi 2
postfix 2
root 412
[root@localhost lib]#

       

    9、某成绩单为:利用awk求每个班的平均分   

        姓名 分数 班级

          a   10   1

          b   20   2

          c   30   1

          d   40   3

          e   50   1

          f   60   2

          g   70   1

          h   80   4

          i   90   1

          j   100  2

          k   110  3

          l   120  2

[root@web ~]# cat file1
a	10	1
b	20	2
c	30	1
d	40	3
e	50	1
f	60	2
g	70	1
h	80	4
i	90	1
j	100	2
k	110	3
l	120	2
[root@web ~]# awk '{score[$3]+=$2;count[$3]++}END{for(i in score){for(j in count){if(i==j)
{print "The Class: ",i," ","AVG_SCORE_IS: ",score[i]/count[j]}}}}' file1
The Class:  4   AVG_SCORE_IS:  80
The Class:  1   AVG_SCORE_IS:  50
The Class:  2   AVG_SCORE_IS:  75
The Class:  3   AVG_SCORE_IS:  75
[root@web ~]#

    10、统计/etc/fstab文件中每个文件系统类型出现的次数

[root@web ~]# awk '/^[^#[:space:]]/{type[$3]++}END{for(i in type){print i,type[i]}}' /etc/fstab
devpts 1
swap 1
sysfs 1
proc 1
tmpfs 1
ext4 4
[root@web ~]#

    

    11、统计/etc/fstab文件中每个单词出现的次数

[root@web ~]# cat /etc/fstab|tr -c '[a-z]' ' '|awk '{for(i=1;i<=NF;i++){count[$i]++}}END{for(j in count){print j,count[j]}}'
cb 1
pages 1
ed 1
ee 2
info 1
disk 1
add 1
devpts 2
mode 1
tmpfs 2
ext 4
ccessible 1

原创文章,作者:M20-1倪文超,如若转载,请注明出处:http://www.178linux.com/48016