• 文本处理工具-AWK


    awk简介

    awk功能与sed相似,都是用来进行文本处理的。awk可以自动地搜索输入文件,并把每一个输入行切分成字段。许多工作都是自动完成的,例如读取每个输入行、字段分割。

    awk工作原理

    awk一次从文本内容中读取一行文本,按输入分隔符进行切,也可以使用-F选项指定分隔符,切成多个组成部分,将每段内容直接保存在内建的变量中$1,$2,$3....$NF(最后一列),引用指定的变量,可以显示指定断,或者多个断。如果需要显示全部的,需要使用$0来引用。可以对单个片断进行判断,也可以对所有断进行循环判断。其默认分隔符为空格

    基础应用示例

    选项:
    -F 指明输入时用到的字段分隔符
    -v var=value: 自定义变量

    awk最常用的格式为:awk [options] 'pattern{action}' file
    最常用的action是:print
    一个示例:

    [root@yufu ~]# df -h
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/sda2       4.8G  1.6G  3.0G  35% /
    /dev/sda1       190M   37M  144M  21% /boot
    /dev/sda3       2.8G  1.1G  1.6G  39% /home
    
    

    处理:awk可以处理多列

    [root@yufu ~]# df -h | awk '{print $1}'
    Filesystem
    /dev/sda2
    /dev/sda1
    /dev/sda3
    [root@yufu ~]# df -h | awk '{print $1,$5}'
    Filesystem Use%
    /dev/sda2 35%
    /dev/sda1 21%
    /dev/sda3 39%
    

    $0与$NF,NF,$NF-1
    $0整行输出

    [root@yufu ~]# df -h | awk '{print $0}'
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/sda2       4.8G  1.6G  3.0G  35% /
    /dev/sda1       190M   37M  144M  21% /boot
    /dev/sda3       2.8G  1.1G  1.6G  39% /home
    
    

    $NF的尾列输出

    [root@yufu ~]# df -h | awk '{print $NF}'
    on
    /
    /boot
    /home
    

    NF是统计每一行的总列数

    [root@yufu ~]# df -h | awk '{print NF}'
    7
    6
    6
    6
    

    $(NF-1)倒数第二列

    [root@yufu ~]# df -h | awk '{print $1,$(NF-1)}'
    Filesystem Mounted
    /dev/sda2 35%
    /dev/sda1 21%
    /dev/sda3 39%
    

    自定义插入字符

    在输出文本列中,还可以添加自己要插入的内容

    默认输出的字段之间间隙比较小,可以在字段键插入tab键分开距离

    [root@yufu ~]# df -h | awk  '{print $1"	"$2}'
    Filesystem	Size
    /dev/sda2	4.8G
    /dev/sda1	190M
    /dev/sda3	2.8G
    

    也可以是输出内容更具可读性

    [root@yufu ~]# df -h | awk  '{print $1" 磁盘大小:"$2}'
    Filesystem 磁盘大小:Size
    /dev/sda2 磁盘大小:4.8G
    /dev/sda1 磁盘大小:190M
    /dev/sda3 磁盘大小:2.8G
    
    

    awk两种特殊模式

    BEGIN和END是awk的两种特殊模式
    BEGIN模式指定了处理文件前要执行的操作
    END模式是指处理完所有行后要执行的操作

    下面用一个示例来看:在磁盘使用率上加上一行标题,最后一行加上end标识

    [root@yufu ~]# df -h | awk 'BEGIN{print "this is disk usage status:"} {print $1"	"$5}'
    this is disk usage status:
    Filesystem	Use%
    /dev/sda2	35%
    /dev/sda1	21%
    /dev/sda3	39%
    [root@yufu ~]# df -h | awk 'BEGIN{print "this is disk usage status:"} {print $1"	"$5} END{print "end"}'
    this is disk usage status:
    Filesystem	Use%
    /dev/sda2	35%
    /dev/sda1	21%
    /dev/sda3	39%
    end
    

    awk分隔符

    awk的分隔符分两种:“输入分隔符”和“输出分隔符”:
    输入分隔符 : 简称 FS;默认的输入分隔符是空格,awk默认以空格为分隔符将文件进行列的分割。

    输出分隔符 简称OFS;awk将文本切割处理后输出在屏幕上,以什么字符作为字段的分隔符?默认也是为空白字符最为输出分隔符。

    在处理文本时,如果文本中没有空白字符那怎么进行分割?这时需要使用输入分隔符指定其他的字符作为分隔符进行切片处理,其实这里的 FS和上面示例中使用的-F作用是一样的,都是指定输入的分割符,同时,FS定义的分隔符可以在Action中进行引用作为输出时插入的内容
    使用FS指定分隔符

    [root@yufu ~]# awk -v FS=':' '{print $1,$3}' /etc/passwd | head -n 3
    root 0
    bin 1
    daemon 2
    [root@yufu ~]# awk -v FS=':' '{print $1FS$3}' /etc/passwd | head -n 3
    root:0
    bin:1
    daemon:2
    
    

    awk处理输出的内容默认是以空白字符作为列的分隔符,如果要指定输出内容的分割符可以使用OFS来指定,一个列子对比

    [root@yufu ~]# df -h | awk '{print $1,$5}'
    Filesystem Use%
    /dev/sda2 35%
    /dev/sda1 21%
    /dev/sda3 39%
    [root@yufu ~]# df -h | awk -v OFS=------ '{print $1,$5}'
    Filesystem------Use%
    /dev/sda2------35%
    /dev/sda1------21%
    /dev/sda3------39%
    
    

    awk变量

    内置变量

    在上面的示例中已经使用到了变量,awk变量分为“内置变量”和“自定义变量”,示例中用到的NF、FS、OFS等就是awk的自定义变量,内置变量是awk预置好的,而自定义变量需要用户自己定义。

    awk常用的内置变量如下:
    FS:输入字段分隔符,默认为空白字符
    OFS:输出字段分隔符,默认为空白字符
    RS:输入记录的分隔符(换行符),指定输入时的换行符
    NF:当前行的字段数(被分割成的列数),$NF 为最后一列,两者不同
    NR:行号,当前处理文本的行号(也可以使用NR指定输出哪一行)
    FNR:各文件分别计数的行号
    FILENAME:当前文件名
    ARGC:命令行参数的个数
    ARGV:数组,保存的是命令行所给定的各个参数
    

    上面已经写了FS和OFS的使用,接着看看RS,RS直白理解就是定义文本内容中以什么作为行的分隔符,有时候会用到这种方式定义行的分割
    一个列子:fstab文件以 双引号 作为行的分隔符

    [root@yufu opt]# cat /etc/fstab
    UUID="6ec772b7-9efb-46d0-80a5-4deaf6d6efe0"	/boot	ext4	defaults	0 0
    UUID="96cb6b8f-c3da-41d1-a063-cfd0e8177085"	/	ext4	defaults	0 0
    UUID="6c323417-2b44-48fd-a4b3-074085b5fe95"    /home	ext4	defaults	0 0
    UUID="fc02879d-56bb-4153-8fa9-c21aa9af8b19"	swap	swap	defaults	0 0
    [root@yufu opt]# cat /etc/fstab | awk -v RS='"' '{print $1}'
    UUID=
    6ec772b7-9efb-46d0-80a5-4deaf6d6efe0
    /boot
    96cb6b8f-c3da-41d1-a063-cfd0e8177085
    /
    6c323417-2b44-48fd-a4b3-074085b5fe95
    /home
    fc02879d-56bb-4153-8fa9-c21aa9af8b19
    swap
    
    

    ORS指定输出分隔符
    OFS可以指定输出时字段间用什么样的字符作为分隔符。默认为空格,这个功能与OFS作用差不多,个人感觉没有OFS好用。两个例子对比
    ORS输出分割(乱遭遭的,看不懂)

    [root@yufu opt]# df -h | awk -v ORS='----' '{print $1,$2}'
    Filesystem Size----/dev/sda2 4.8G----/dev/sda1 190M----/dev/sda3 2.8G
    

    OFS输出分割

    [root@yufu opt]# df -h | awk -v OFS=---- '{print $1,$2}'
    Filesystem----Size
    /dev/sda2----4.8G
    /dev/sda1----190M
    /dev/sda3----2.8G
    

    NF和$NF的用法
    稍不注意会被这两个给搞混了,NF是统计一行中的列数,而$NF是取一行中的最后一列
    一个例子对比

    [root@yufu opt]# cat /etc/fstab 
    UUID="6ec772b7-9efb-46d0-80a5-4deaf6d6efe0"	/boot	ext4	defaults	0 0
    UUID="96cb6b8f-c3da-41d1-a063-cfd0e8177085"	/	ext4	defaults	0 0
    UUID="6c323417-2b44-48fd-a4b3-074085b5fe95"    /home	ext4	defaults	0 0
    UUID="fc02879d-56bb-4153-8fa9-c21aa9af8b19"	swap	swap	defaults	0 0
    [root@yufu opt]#  awk '{print NF,$NF}' /etc/fstab 
    6 0
    6 0
    6 0
    6 0
    

    每行以空格分割,一个6列。最后一列是0

    NR行号处理
    NR可以用来显示处理行的行号,也可以用来匹配指定行的内容

    示例

    [root@yufu opt]# df -h | awk '{print NR,$0}'
    1 Filesystem      Size  Used Avail Use% Mounted on
    2 /dev/sda2       4.8G  1.6G  3.0G  36% /
    3 /dev/sda1       190M   37M  144M  21% /boot
    4 /dev/sda3       2.8G  1.1G  1.6G  39% /home
    

    指定行号处理

    [root@yufu opt]# df -h | awk 'NR==2 {print $1,$5}'
    /dev/sda2 36%
    

    指定行号输出

    [root@yufu opt]# df -h | awk 'NR==2'
    /dev/sda2       4.8G  1.6G  3.0G  36% /
    

    FNR:用于分别显示两个文件行号

    [root@yufu opt]# awk '{print FNR,$0}' fi.txt fr.txt
    1 hfjg
    2 jsklyhfg
    3 kifg
    1 jkdfgufgjk
    2 sdfg
    

    ARGV内置变量表示的是一个数组,这个数组中保存的是命令行所给定的参数,数组的下标从0开始
    一个列子

    [root@yufu opt]# awk 'BEGIN{print "aaa",ARGV[1]}' fi.txt fr.txt
    aaa fi.txt
    [root@yufu opt]# awk 'BEGIN{print "aaa",ARGV[0]}' fi.txt fr.txt
    aaa awk
    [root@yufu opt]# awk 'BEGIN{print "aaa",ARGV[2]}' fi.txt fr.txt
    aaa fr.txt
    

    自定义变量

    自定义变量容易理解,就是我们自己定义的变量,定义的方法:
    自定义变量: -v varname=value (变量名区分大小写)

    [root@yufu opt]# awk -v myvar="gudaoyufu" 'BEGIN{print myvar}'
    gudaoyufu
    

    或者

    [root@yufu opt]# awk  'BEGIN{myvar="gudaoyufu";print myvar}'
    gudaoyufu
    

    awk格式化输出

    在输出时可以是内容一一些特定的格式输出,格式化输出则需要使用printf来指定,功能与print相似,只是这个可以支持格式化输出
    格式化输出:printf “FORMAT” , item1, item2, ...
    (1) 必须指定FORMAT
    (2) 不会自动换行,需要显式给出换行控制符,
    (3) FORMAT中需要分别为后面每个item指定格式符
    格式符:与item一一对应

    %c: 显示字符的ASCII码
    %d, %i: 显示十进制整数
    %e, %E:显示科学计数法数值
    %f:显示为浮点数
    %g, %G:以科学计数法或浮点形式显示数值
    %s:显示字符串
    %u:无符号整数
    %%: 显示%自身
    

    修饰符:

    #[.#]:第一个数字控制显示的宽度;第二个#表示小数点后精度,%3.1f

    -: 左对齐(默认右对齐) %-15s
    +:显示数值的正负符号 %+d
    

    printf示例

    [root@yufu opt]# awk -F : '{printf "%-15s %-10d
    ",$1,$3}' /etc/passwd | head -n 4
    root            0
    bin             1
    daemon          2
    adm             3
    
    [root@yufu opt]# awk -F : '{printf "-15s UID:%-10d
    ",$1,$31}' /etc/passwd | head -n 4
    root            UID:0
    bin             UID:1
    daemon          UID:2
    adm             UID:3
    

    awk运算操作

    上面有个示例:

    [root@yufu opt]# awk 'NR==2 {print $1}' /etc/fstab 
    UUID="96cb6b8f-c3da-41d1-a063-cfd0e8177085"
    

    这个就使用到了运算符,“NR==2”,获取一个行并做处理,awk支持多种运算处理
    算术操作符:

    x+y, x-y, x*y, x/y, x^y, x%y
    -x: 转换为负数
    +x: 转换为数值
    

    字符串操作符:没有符号的操作符,字符串连接

    赋值操作符:

    =, +=, -=, *=, /=, %=, ^=
    ++, --
    

    比较操作符:
    ==, !=, >, >=, <, <=

    逻辑操作符:与&&,或||,非!

    简单示例

    [root@yufu opt]# awk -F :  '$3>=600 {print $1,$3}' /etc/passwd
    suzhou 600
    yufu 601
    guoguo 602
    user2 604
    user3 605
    user4 606
    user5 607
    user6 608
    user7 609
    user8 610
    user9 611
    user10 612
    
    [root@yufu opt]# awk -F :  '$3 <100 || $3 >=600 {print $1,$3}' /etc/passwd
    root 0
    bin 1
    dbus 81
    vcsa 69
    postfix 89
    sshd 74
    oprofile 16
    ... ...
    suzhou 600
    yufu 601
    guoguo 602
    

    匹配磁盘使用情况

    [root@yufu opt]# df -h|awk -F% '/^/dev/{print $1}'|awk '$NF>=30{print $1,$5}'
    /dev/sda2 36
    /dev/sda3 39
    

    awk的PATTERN匹配

    PATTERN:根据pattern条件,过滤匹配的行,再做处理,PATTERN中可以使用正则表达式来进行文本匹配
    ,例如,匹配passwd中以user开头的用户

    [root@yufu opt]# awk '/^user/{print $0}' /etc/passwd
    user2:x:604:604::/home/user2:/bin/bash
    user3:x:605:605::/home/user3:/bin/bash
    user4:x:606:606::/home/user4:/bin/bash
    user5:x:607:607::/home/user5:/bin/bash
    user6:x:608:608::/home/user6:/bin/bash
    user7:x:609:609::/home/user7:/bin/bash
    user8:x:610:610::/home/user8:/bin/bash
    user9:x:611:611::/home/user9:/bin/bash
    user10:x:612:612::/home/user10:/bin/bash
    

    上面这个功能实现了grep的匹配功能,但它的使用与grep还是有些区别的:

    grep "正则表达式" /etc/passwd
    awk '/正则表达式/ {print $0}' /etc/passwd

    在awk使用正则表达式时特殊字符要使用转义符;如下:

    [root@yufu opt]# awk '//bin/bash$/ {print $0}' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    suzhou:x:600:600::/home/feng:/bin/bash
    tomcat:x:501:501::/home/tomcat:/bin/bash
    yufu:x:601:601::/home/yufu:/bin/bash
    guoguo:x:602:602::/home/guoguo:/bin/bash
    user2:x:604:604::/home/user2:/bin/bash
    ... ...
    

    结构控制语句

    在awk中我们同样可以if这样的语法进行条件判断,只是在awk中使用if判断条件是在一行中完成,下面的一个示例:查看使用率超过30的分区

    [root@yufu ~]# df -h | awk  '/^/dev/sd/{if ($5>=30){print $1,$5}}'
    /dev/sda2 36%
    /dev/sda3 39%
    

    如果判断条件不成立则进行下面的动作:

    [root@yufu ~]# df -h | awk  '/^/dev/sd/{if ($5>=50){print $1,$5}else{print $1,$5}}'
    /dev/sda2 36%
    /dev/sda1 21%
    /dev/sda3 39%
    

    awk数组使用

    数组的格式:
    name[0]="xiaoming"
    name[1]="laowang"
    上列中name为数组名,[0],[1]为元素下标 ,“小明”为数组元素

    定义并打印一个简单的数组

    [root@yufu ~]# awk 'BEGIN {a[0]="gudao";a[1]="yufu";print a[0],a[1]}'
    gudao yufu
    

    打印数的下标

    [root@yufu ~]# awk 'BEGIN {a[0]="gudao";a[1]="yufu";for (i in a){print i}}'
    0
    1
    

    把数组的下标和元素一起打印

    [root@yufu ~]# awk 'BEGIN {a[0]="gudao";a[1]="yufu";for (i in a){print i, a[1]}}'
    0 yufu
    1 yufu
    

    数组遍历

    数组遍历需要使用for循环来配合处理,下面以一个示例看看awk的数组遍历的使用:
    示例:一个web日志文件有60多万条的记录,怎样统计日志里每个ip的访问次数呢?

    [root@yufu ~]# head access_log 
    192.168.30.6 - - [14/May/2018:14:21:41 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
    

    方法一:

    [root@yufu ~]# cat access_log | awk '{print $1}' | sort |uniq -c | sort -nr 
     295764 172.20.109.251
     170000 172.20.109.233
     100000 172.20.101.240
      39243 172.20.111.90
      16000 172.20.109.164
       8000 172.20.102.79
       6062 172.20.111.9
       6000 172.20.103.69
       4031 172.20.101.23
       4014 192.168.30.6
       4000 172.20.1.24
       2056 172.20.111.65
       2020 172.20.111.164
       2002 172.20.110.34
       2000 172.20.111.162
        400 172.20.75.66
         58 ::1
         10 192.168.30.1
    

    方法二:用AWK数组遍历来统计

    [root@yufu ~]# awk '{num[$1]++}END{for(i in num){print i,num[i]}}' access_log | sort -nr -k2
    172.20.109.251	295764
    172.20.109.233	170000
    172.20.101.240	100000
    172.20.111.90	39243
    172.20.109.164	16000
    172.20.102.79	8000
    172.20.111.9	6062
    172.20.103.69	6000
    172.20.101.23	4031
    192.168.30.6	4014
    172.20.1.24	    4000
    172.20.111.65	2056
    172.20.111.164	2020
    172.20.110.34	2002
    172.20.111.162	2000
    172.20.75.66	400
    ::1	58
    192.168.30.1	10
    
    

    理解上面的例子:
    awk中使用的num[$1]++ :数组中将日志文件的第一列作为数组num的下标,awk在读取第一行时会读取到这个数组,此时数组是这样的:num[172.20.109.251]++,由于后边的++运算符,awk会将数字0自动赋值给num[172.20.109.251]然后再做++运算。
    此时num[172.20.109.251]做++,也就是0+1=1
    当在读到第二个172.20.109.251时,此时num[172.20.109.251]的值已经为1,再做一次—++运算就是1+1得到的值为2,就这样以此类推
    最后的++运算的值是多少,也就意味着172.20.109.251这个ip被运算了多少次,也就意味着这个地址出现了多少次。
    最后通过for循环把num数组的下标(ip)和出现的次数都打印出来

    同样,可以利用awk数组统计tcp的连接数:

    [root@centos ~]# netstat -ant | awk '/^tcp/{state[$NF]++}END{for(i in state){print i,"	"state[i]}}'
    TIME_WAIT 	19
    CLOSE_WAIT 	1
    ESTABLISHED 	5
    LISTEN 	11
    

    还可以统计当前系的80端口的连接数与远程连接的ip

    [root@web ~]# netstat -nat | grep "ESTABLISHED" | awk '/^tcp/{state[$(NF-2)]++}END{for(a in state){print a "  " state[a]}}' | grep ':80'
    192.168.19.47:80  4
    
  • 相关阅读:
    Lookup注解
    解决数据量大,分页查询慢的方案
    一个http的请求分析
    MYSQL
    什么是性能优化
    编码规范随笔
    装饰者模式
    单例模式
    J.U.C并发包(1)
    Java内存模型
  • 原文地址:https://www.cnblogs.com/anay/p/9049839.html
Copyright © 2020-2023  润新知