1. awk基础概念

awk是一个强大的文本分析工具,相对于grep的查找 , sed的编辑 , awk在其对数据分析并生成报告时 , 显得尤为强大。简单来说awk就是把文件逐行的读入 , 以空格为默认分隔符将每行切片 ,切开的部分再进行各种分析处理。 awk有3个不同版本: awknawkgawk,未作特别说明,一般指gawkgawkAWKGNU 版本 awk其名称得自于它的创始人 Alfred Aho 、Peter WeinbergerBrian Kernighan 姓氏的首个字母。实际上 AWK 的确拥有自己的语言 : AWK 程序设计语言 , 三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序 , 这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表 , 还有无数其他的功能。

1.1 awk语法

  • 基本用法

    • awk [options] 'program' file…

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

    • awk [options] 'BEGIN{ action;… } pattern{ action;… } END{ action;… }' file ...

  • 说明 :

    • program通常是被放在单引号中,并可以由三种部分组成

      • BEGIN 语句块

      • 模式匹配的通用语句块

      • END 语句块

  • 选项

    • -F : 指明输入时用到的字段分隔符

    • -v var=value : 自定义变量

  • program:pattern{action statements;..}

    • patternaction

      • pattern部分决定动作语句何时触发及触发事件 BEGIN,END

      • action statements对数据进行处理,放在{}内指明 print, printf

awk 程序通常由 : BEGIN语句块能够使用模式匹配的通用语句块 、END语句块,共3部分组成

1.2 分割符、域和记录

awk执行时 , 由分隔符分隔的字段,field)标记$1,$2..$n称 为域标识。$0为所有域 , 注意 : 和shell中变量$符含义不同。 ​ 文件的每一行称为记录(Record)

省略action , 则默认执行 print $0 的操作

1.3 AWK工作原理

1.3.1 处理流程

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

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

  • 第三步:当读至输入流末尾时,执行END{action;…}语句块。

1.3.2 步骤说明

  • BEGIN语句块awk开始从输入流中读取行之前被执行 , 这是一个 可选的语句块 , 比如变量初始化打印输出表格的表头等语句通常 可以写在BEGIN语句块中

  • END语句块awk从输入流中读取完所有的行之后即被执行 , 比如 打印所有行的分析结果这类信息汇总都是在END语句块中完成 , 它 也是一个可选语句块

  • pattern语句块中的通用命令是最重要的部分 , 也是可选的。如果 没有提供pattern语句块 , 则默认执行{ print } , 即打印每一个读取 到的行 , awk读取的每一行都会执行该语句块

2. print和printf

2.1 print

  • 格式: print item1, item2, ...

  • 要点

    1. 逗号分隔符

    2. 输出的各item可以是字符串,也可以是数值 ;当前记录的字段变量awk的表达式

    3. 如省略item , 相当于print $0

    4. 引用的变量想要被替换不允许被引号括起来

范例 :

[root@centos8 ~]#seq 10 | awk '{print "hello,awk"}'
hello,awk
hello,awk
hello,awk
hello,awk
hello,awk
hello,awk
hello,awk
hello,awk
hello,awk
hello,awk  
[root@centos8 ~]#seq 3 | awk '{print 2*3}'
6
6
6
​
#每一行均打印为wang,打印行数和/etc/passwd文件中行数一样
[root@centos8 ~]#awk -F: '{print "wang"}' /etc/passwd
​
#打印/etc/passwd中的行,类似于cat /etc/passwd
[root@centos8 ~]# awk -F: '{print}' /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
...
[root@centos8 ~]#awk -F: '{print $0}' /etc/passwd 
​
#以:为分隔符,并打印/etc/passwd中的第一列
[root@centos8 ~]#awk -F: '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
...
​
#以:为分隔符,打印第1列和第3列,中间有一个制表符
[root@localhost ~]# awk -F: '{print $1"\t"$3}' /etc/passwd
root    0
bin 1
daemon  2
adm 3
lp  4
...
​
​
[root@centos8 ~]#grep "^UUID" /etc/fstab |awk {'print $2,$3'}
/ xfs
/boot ext4
/data xfs
swap swap

面试题:取出网站访问量最大的前3个IP

[root@VM_0_10_centos logs]# awk '{print $1}' nginx.access.log-20200428|sort | 
uniq -c |sort -nr|head -3
   5498 122.51.38.20
   2161 117.157.173.214
   953 211.159.177.120
​
[root@centos8 ~]#awk '{print $1}' access_log |sort |uniq -c|sort -nr|head 
   4870 172.20.116.228
   3429 172.20.116.208
   2834 172.20.0.222
   2613 172.20.112.14
   2267 172.20.0.227
   2262 172.20.116.179
   2259 172.20.65.65
   1565 172.20.0.76
   1482 172.20.0.200
   1110 172.20.28.145

面试题 : 取磁盘利用率

[root@centos8 ~]#df | awk '{print $1,$5}' 
Filesystem Use%
devtmpfs 0%
tmpfs 0%
tmpfs 2%
tmpfs 0%
/dev/sda2 3%
/dev/sda3 1%
/dev/sda1 15%
tmpfs 0%
​
#使用扩展的正则表达式
[root@centos8 ~]#df | awk -F"[[:space:]]+|%" '{print $5}'
Use
0
0
1
0
5
1
92
1
[root@centos8 ~]#df | awk -F" +|%" '{print $5}' #需要注意这命令和上面那个命令一样,只是将[[:space:]]替换为"␣"这种单纯的空格键,命令中的空白字符实际是生效的
[root@centos8 ~]#df | awk -F'[[:space:]]+|%' '{print $1,$5}'  
Filesystem Use
devtmpfs 0
tmpfs 0
tmpfs 2
tmpfs 0
/dev/sda2 3
/dev/sda3 1
/dev/sda1 15
tmpfs 0
​
[root@centos8 ~]#df | grep "^/dev/sd" | awk -F"[[:space:]]+|%" '{print $5}'
51
92
[root@centos8 ~]#df | grep '^/dev/sd'| awk -F'[[:space:]]+|%' '{print $1,$5}'  
/dev/sda2 3
/dev/sda3 1
/dev/sda1 15

面试题:取 ifconfifig 输出结果中的IP地址

[root@centos8 ~]#ifconfig eth0 | awk '/netmask/{print $2}'
10.0.0.8
​
[root@centos6 ~]#ifconfig eth0 |awk -F " +|:" '/Mask/{print $4}'
10.0.0.6
​
[root@centos8 ~]#ifconfig eth0| sed -rn '2s/^[^0-9]+([0-9.]+) .*$/\1/p'
10.0.0.8
​
[root@centos6 ~]#ifconfig eth0| sed -rn '2s/^[^0-9]+([0-9.]+) .*$/\1/p'
10.0.0.6

面试题 : 文件host_list.log 如下格式,请提取”.magedu.com”前面的主机名部分并写入到回到该文件中

[root@centos8 ~]#cat host_list.log
1 www.magedu.com
2 blog.magedu.com
3 study.magedu.com
4 linux.magedu.com
5 python.magedu.com
[root@centos8 ~]#awk -F"[ .]" '{print $2}' host_list.log
www
blog
study
linux
python
[root@centos8 ~]#awk -F"[ .]" '{print $2}' host_list.log >> host_list.log 
[root@centos8 ~]#cat host_list.log
1 www.magedu.com
2 blog.magedu.com
3 study.magedu.com
4 linux.magedu.com
5 python.magedu.com
www
blog
study
linux
python

2.2 printf

  • 格式 : printf “FORMAT”, item1, item2, ...

  • 使用注意点 :

    1. 必须指定FORMAT

    2. 不会自动换行,需要显式给出换行控制符,\n

    3. FORMAT中需要分别为后面每个item指定格式符

  • 格式符:与item一一对应

    • %c: 显示字符的ASCII码

    • %d, %i: 显示十进制整数

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

    • %f:显示为浮点数

    • %g, %G:以科学计数法或浮点形式显示数值

    • %s:显示字符串

    • %u:无符号整数

    • %%: 显示%自身

  • 修饰符

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

    • -: 左对齐(默认右对齐%-15s

    • +:显示数值的正负符号 %+d

awk -F: '{printf "%s",$1}' /etc/passwd 
awk -F: '{printf "%s\n",$1}' /etc/passwd 
awk -F: '{printf "%-20s %10d\n",$1,$3}' /etc/passwd 
awk -F: '{printf "Username: %s\n",$1}'  /etc/passwd 
awk -F: '{printf "Username: %s,UID:%d\n",$1,$3}'  /etc/passwd 
awk -F: '{printf "Username: %15s,UID:%d\n",$1,$3}'   /etc/passwd 
awk -F: '{printf "Username: %-15s,UID:%d\n",$1,$3}'   /etc/passwd 

3. awk中的变量

3.1 内置变量

  • FS:(Filed Separator)输入字段分隔符默认为空白字符

    [root@centos8 ~]# awk -v FS=':'  '{print $1,FS,$3}'  /etc/passwd | head -n 3
    root : 0
    bin : 1
    daemon : 2
    [root@centos8 ~]# awk -v FS=':'  '{print $1FS$3}'  /etc/passwd | head -n 3
    root:0
    bin:1
    daemon:2
    [root@centos8 ~]# awk -v FS=':'  '{print $1,$3,$7}'  /etc/passwd | head -n 3
    root 0 /bin/bash
    bin 1 /sbin/nologin
    daemon 2 /sbin/nologin
    [root@centos8 ~]# S=:;awk -F$S '{print $1,$3}' /etc/passwd | head -n 3
    root 0
    bin 1
    daemon 2
    ​
    # -F 和FS变量功能一样,同时使用会冲突
    [root@centos8 ~]# awk -v FS=":" -F";" '{print $1FS$3}' /etc/passwd | head -n 3
    root:x:0:0:root:/root:/bin/bash;
    bin:x:1:1:bin:/bin:/sbin/nologin;
    daemon:x:2:2:daemon:/sbin:/sbin/nologin;
    [root@centos8 ~]# awk -F";" -v FS=":" '{print $1FS$3}' /etc/passwd | head -n 3
    root:0
    bin:1
    daemon:2
    ​

  • OFS:(Output Filed Separator)输出字段分隔符,默认为空白字符

    [root@centos8 ~]# awk -v FS=':' '{print $1,$3,$7}'  /etc/passwd | head -n1
    root 0 /bin/bash
    [root@centos8 ~]# awk -v FS=':' -v OFS=':' '{print $1,$3,$7}'  /etc/passwd | head -n1
    root:0:/bin/bash

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

    [root@centos8 tmp]# cat /root/test 
    1 www.magedu.com
    2 blog.magedu.com
    3 study.magedu.com
    4 linux.magedu.com
    5 python.magedu.com
    [root@centos8 tmp]#
    #可以看到当读到最后一行时,即使文本中没有换行符之后还是会添加一个换行符
    
    [root@centos8 ~]# awk -v RS=' '  '{print }'  /root/test
    1
    www.magedu.com
    2
    blog.magedu.com
    3
    study.magedu.com
    4
    linux.magedu.com
    5
    python.magedu.com 
    # 1 单独为一行,之后www.magedu.com + 换行符 +2单独为一行,以此类推

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

    [root@centos8 ~]# awk -v RS=' ' -v ORS='###' '{print $0}' /root/test
    1###www.magedu.com
    2###blog.magedu.com
    3###study.magedu.com
    4###linux.magedu.com
    5###python.magedu.com
    ###[root@centos8 ~]# 
    

  • NF:(Number of Filed)字段数量

    #引用变量时,变量前不需加$
    [root@centos8 ~]# awk -F: '{print NF}' /etc/fstab 
    0
    1
    1
    3
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    [root@centos8 ~]# awk -F: '{print NF}' /etc/passwd
    7
    7
    7
    7
    ...
    
    #打印各个用户的家目录信息
    [root@centos8 ~]# awk -F: '{print $(NF-1)}' /etc/passwd
    /root
    /bin
    /sbin
    /var/adm
    /var/spool/lpd
    /sbin
    /sbin
    /sbin
    /var/spool/mail
    ...

    面试题 : 显示连接数最多的前3个IP

    [root@centos8 ~]#awk -F" +|:" '/^ESTAB/{print $(NF-2)}' ss.log |sort |uniq -
    c|sort -nr|head -n3
         12 223.88.255.148
         10 183.202.63.36
         9 117.152.155.119
    [root@centos8 ~]#ss -nt |grep "^ESTAB" | awk -F"[[:space:]]+|:" '{print $(NF-
    2)}'
    10.0.0.1
    10.0.0.7
    10.0.0.1
    [root@centos8 ~]#ss -nt |awk -F"[[:space:]]+|:" '/^ESTAB/{print $(NF-2)}'
    [root@centos8 ~]#ss -nt|awk -F: '{print $(NF-1)}' |awk '/^[0-9]/{print $NF}'| 
    sort |uniq -c |head -n 3

    范例 : 每十分钟检查将连接数超过100个以上的IP放入黑名单拒绝访问

    [root@centos8 ~]#cat deny_dos.sh
    LINK=100
    while true;do
     ss -nt | awk -F"[[:space:]]+|:" '/^ESTAB/{print $(NF-2)}'|sort |uniq - c|while read count ip;do 
     if [ $count -gt $LINK ];then
       iptables -A INPUT -s $ip -j REJECT
     fi
     done
    done
    
    [root@centos8 ~]#cat deny_dos.sh
    IPLIST=`awk -F" +|:" '/^ESTAB/{print $(NF-2)}' ss.log |sort |uniq -c|sort -
    nr|head -3|awk '{print $2}'`
    for ip in $IPLIST;do
       iptables -A INPUT -s $ip -j REJECT
    done

  • NR : (Number of Record)记录的编号 (行号)

    范例 :

    [root@centos8 ~]# awk '{print NR,$0}' /etc/issue
    1 \S
    2 Kernel \r on an \m
    3 
    [root@centos8 ~]# awk '{print NR}' /etc/passwd
    1
    2
    3
    4
    5
    6
    ...
    
    [root@centos8 ~]# awk -F: 'BEGIN{print NR}' /etc/passwd
    0
    [root@centos8 ~]# awk -F: 'END{print NR}' /etc/passwd
    45

    范例 : 取ifconfig输出结果中的IP地址

    [root@centos8 ~]#ifconfig eth0 | awk '/netmask/{print $2}'
    10.0.0.8
    [root@centos8 ~]#ifconfig eth0 | awk 'NR==2{print $2}'
    10.0.0.8

  • FNR:分别打印所有文件的行号(各文件分别计数,记录号 )

    [root@centos8 ~]# awk '{print FNR,$0}' /etc/issue /etc/centos-release
    1 \S
    2 Kernel \r on an \m
    3 
    1 CentOS Linux release 8.2.2004 (Core) 
    [root@centos8 ~]# awk '{print NR,$0}' /etc/issue /etc/centos-release
    1 \S
    2 Kernel \r on an \m
    3 
    4 CentOS Linux release 8.2.2004 (Core) 
  • FILENAME:当前文件名

    [root@centos8 ~]# awk '{print FILENAME}' /etc/issue
    /etc/issue
    /etc/issue
    /etc/issue
    
    [root@centos8 ~]# awk '{print FNR,FILENAME,$0}' /etc/issue /etc/redhat-release
    1 /etc/issue \S
    2 /etc/issue Kernel \r on an \m
    3 /etc/issue 
    1 /etc/redhat-release CentOS Linux release 8.2.2004 (Core) 
    
  • ARGC:命令行参数的个数

    [root@centos8 ~]# awk '{print ARGC}' /etc/issue /etc/redhat-release
    3
    3
    3
    3
    [root@centos8 ~]# awk 'BEGIN{print ARGC}' /etc/issue /etc/redhat-release
    3
  • ARGV数组,保存的是命令行所给定的各参数 ,每一个参数:ARGV[0],......

    [root@centos8 ~]# awk 'BEGIN{print ARGV[0]}' /etc/issue /etc/redhat-release
    awk
    [root@centos8 ~]# awk 'BEGIN{print ARGV[1]}' /etc/issue /etc/redhat-release
    /etc/issue
    [root@centos8 ~]# awk 'BEGIN{print ARGV[2]}' /etc/issue /etc/redhat-release
    /etc/redhat-release
    [root@centos8 ~]# awk 'BEGIN{print ARGV[3]}' /etc/issue /etc/redhat-release
    
    [root@centos8 ~]# 

引用内置变量不用$

3.2 自定义变量

  • 自定义变量(区分字符大小写)

    1. -v var=value

    2. program中直接定义

示例:

[root@centos8 ~]# awk -v test='hello gawk' '{print test}' /etc/issue
hello gawk
hello gawk
hello gawk

[root@centos8 ~]# awk -v test='hello gawk' 'BEGIN{print test}' 
hello gawk

[root@centos8 ~]# awk 'BEGIN{test="hello,gawk";print test}'
hello,gawk
 
[root@centos8 ~]# awk -F':' '{sex="male";print $1,sex,age;age=18}' /etc/passwd
root male 
bin male 18
daemon male 18
adm male 18
lp male 18
...

[root@centos8 ~]# sep=':';awk -v FS="$sep" '{print $1,FS,$3}' /etc/passwd
root : 0
bin : 1
daemon : 2
adm : 3
lp : 4
sync : 5
...


[root@centos8 ~]# sep=':';awk -F "$sep" '{print $1,":",$3}' /etc/passwd
root : 0
bin : 1
daemon : 2
adm : 3
...

[root@centos8 ~]# awk -v sep=":" -F: '{print $1sep$3}' /etc/passwd
root:0
bin:1
daemon:2
adm:3
...

[root@centos8 ~]# awk -v user="username" -v uuid="uuid" -F: '{print user":"$1"\t"uuid":"$3}' /etc/passwd
username:root	uuid:0
username:bin	uuid:1
...

[root@centos8 ~]# u="user";awk -v username="$u" -F: '{uid="uid";print username":"$1"\t"uid":"$3}' /etc/passwd
user:root	uid:0
user:bin	uid:1
user:daemon	uid:2
user:adm	uid:3
...
# 可以调用系统中本身的变量

[root@centos8 ~]# cat awkscript 
{print script,$1,$2}
[root@centos8 ~]# awk -F: -f awkscript  script="awk" /etc/passwd
awk root x
awk bin x
awk daemon x
awk adm x
...

4. 操作符

  • 算术操作符

    • x+y, x-y, x*y, x/y, x^y, x%y

    • -x: 转换为负数

    • +x: 转换为数值

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

  • 赋值操作符=, +=, -=, *=, /=, %=, ^=, ++, --

    [root@centos8 ~]# awk 'BEGIN{i=0;print i++,i}'
    0 1
    [root@centos8 ~]# awk 'BEGIN{i=0;print ++i,i}'
    1 1
    
    [root@centos8 ~]# awk -v n=0 '!n++' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    #只打印第一行
    
    [root@centos8 ~]# awk -v n=0 '!n++{print n}' /etc/passwd
    1
    
    [root@centos8 ~]# awk -v n=0 '!++n' /etc/passwd
    [root@centos8 ~]#
    #没有输出结果
    
    [root@centos8 ~]# awk -v n=-1 '!++n' /etc/passwd
    root:x:0:0:root:/root:/bin/bash

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

    范例

    [root@centos8 ~]# awk 'NR==2' /etc/issue
    Kernel \r on an \m
    
    [root@centos8 ~]# awk -F: '$3>=1000' /etc/passwd
    nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
    xiong:x:1000:1000:xiong:/home/xiong:/bin/bash

    范例 : 取奇,偶数行

    [root@centos8 ~]# seq 10 | awk 'NR%2==0'
    2
    4
    6
    8
    10
    
    [root@centos8 ~]# seq 10 | awk 'NR%2==1'
    1
    3
    5
    7
    9
    
    [root@centos8 ~]# seq 10 | awk 'NR%2!=0'
    1
    3
    5
    7
    9
    

  • 模式匹配符

    • ~:左边是否和右边匹配包含

    • !~:是否不匹配

    [root@centos8 ~]# awk -F: '$0 ~ /^root/{print $0}' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    [root@centos8 ~]# awk -F: '$0 ~ "^root"{print $0}' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    [root@centos8 ~]# awk -F: '$0 !~ "^root"{print $0}' /etc/passwd
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    ...
    
    [root@centos8 ~]# awk '/root/' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin
    
    [root@centos8 ~]#df | awk -F"[[:space:]]+|%" '$0 ~ /^\/dev\/sd/{print $5}'
    5
    1
    92
    
    [root@centos8 ~]# ifconfig ens33 | awk 'NR==2{print $2}'
    192.168.74.128 
  • 逻辑操作符:与&&或||非!

    范例

    [root@centos8 ~]# awk 'BEGIN{print i}'
    
    [root@centos8 ~]# awk 'BEGIN{print !i}'
    1
    [root@centos8 ~]# awk -v i=10 'BEGIN{print !i}'
    0
    [root@centos8 ~]# awk -v i=-3 'BEGIN{print !i}'
    0
    [root@centos8 ~]# awk -v i=0 'BEGIN{print !i}'
    1
    [root@centos8 ~]# awk -v i=abc 'BEGIN{print !i}'
    0
    [root@centos8 ~]# awk -v i='' 'BEGIN{print !i}'
    1

    范例

    awk –F: '$3>=0 && $3<=1000 {print $1}' /etc/passwd  
    awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
    awk -F: '!($3==0) {print $1}' /etc/passwd
    awk -F: '!($3>=500) {print $3}' /etc/passwd

  • 条件表达式三目表达式):

    • selector?if-true-expression:if-false-expression

      awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or SysUser";printf "%15s:%-s\n",$1,usertype}' /etc/passwd

5. Program

  • 格式 : awk [options] 'program' file…

  • program : pattern{action statements;..}

    • patternaction

      • pattern部分决定动作语句何时触发及触发事件 BEGIN,END

      • action statements对数据进行处理,放在{}内指明 print, printf

5.1 Program:PATTERN

PATTERN : 根据pattern条件 , 过滤匹配的行,再做处理

  • 如果未指定:空模式,匹配每一行

  • /regular expression/:仅处理能够模式匹配到的行,需要用/ /括起来

    [root@centos8 ~]# awk '/^UUID/{print $1}' /etc/fstab 
    UUID=16ab6947-566e-40c3-94f7-ce21c0404cb1
    
    [root@centos8 ~]# awk '!/^UUID/{print $1}' /etc/fstab 
    
    #
    #
    #
    #
    #
    #
    #
    #
    #
    #
    /dev/mapper/cl-root
    /dev/mapper/cl-swap
    
    [root@centos8 ~]#df | awk '/^\/dev\/sd/'
    /dev/sda2      104806400 4935924  99870476   5% /
    /dev/sda3       52403200  398876  52004324   1% /data
    /dev/sda1         999320  848572     81936  92% /boot
  • relational expression: 关系表达式,结果为“真”才会被处理

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

    • :结果为空字符串或0值

      范例

      [root@centos8 ~]# awk '!1' /etc/issue   #没有输出结果
      [root@centos8 ~]# awk '0' /etc/issue
      [root@centos8 ~]# awk '!0' /etc/issue
      \S
      Kernel \r on an \m
      
      [root@centos8 ~]# awk '1' /etc/issue
      \S
      Kernel \r on an \m
      
      [root@centos8 ~]# awk '""' /etc/issue
      [root@centos8 ~]# awk -v magedu="" 'magedu' /etc/issue
      [root@centos8 ~]# awk -v magedu="0" 'magedu' /etc/issue
      [root@centos8 ~]# awk -v magedu=0 'magedu' /etc/issue
      [root@centos8 ~]# awk 'magedu' /etc/issue
      [root@centos8 ~]# awk -v magedu=wang 'magedu' /etc/issue
      \S
      Kernel \r on an \m
      
      [root@centos8 ~]# awk '"0"' /etc/issue
      \S
      Kernel \r on an \m
      
      

      范例 :

      [root@centos8 ~]# awk -F: 'i=1;j=1{print i,j}' /etc/issue
      \S
      1 1
      Kernel \r on an \m
      1 1
      
      1 1
      [root@centos8 ~]# awk -F: '$3>=1000{print $1,$3}' /etc/passwd
      nobody 65534
      xiong 1000
      [root@centos8 ~]# awk -F: '$3<1000{print $1,$3}' /etc/passwd
      root 0
      bin 1
      daemon 2
      adm 3
      ...
      [root@centos8 ~]# awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
      root /bin/bash
      xiong /bin/bash
      
      [root@centos8 ~]# awk -F: '$NF ~ /bash$/{print $1,$NF}' /etc/passwd
      root /bin/bash
      xiong /bin/bash

  • line ranges:行范围

    • startline,endline/pat1/,/pat2/ 不支持直接给出数字

    • 格式

      • awk -F: ‘/^root\>/,/^nobody\>/{print $1}' /etc/passwd

      • awk -F: ‘(NR>=10&&NR<=20){print NR,$1}' /etc/passwd

  • BEGIN/END模式

    • BEGIN{}: 仅在开始处理文件中的文本之前执行一次

    • END{}:仅在文本处理完成之后执行一次

  • 函数调用function_name(argu1, argu2, ...)

[root@localhost ~]# awk -F:  'i=1;j=1{print i,j}' /etc/passwd 
root:x:0:0:root:/root:/bin/bash
1 1
bin:x:1:1:bin:/bin:/sbin/nologin
1 1
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1 1
adm:x:3:4:adm:/var/adm:/sbin/nologin
1 1
[root@localhost ~]#awk  '!0'  /etc/passwd 
#打印该文件中的文件内容
[root@localhost ~]#awk  '!1'   /etc/passwd 
#不打印该文件中的文件内容
[root@localhost ~]awk –F: '$3>=1000{print $1,$3}'  /etc/passwd 
#打印UID大于1000以上的用户信息
[root@localhost ~]awk -F: '$3<1000{print $1,$3}'  /etc/passwd 
#打印UID小于1000以下的用户信息
[root@localhost ~]awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd 
#打印登陆shell为/bin/bash的用户信息
[root@localhost ~]awk -F: '$NF ~ /bash$/{print $1,$NF}' /etc/passwd
#打印登陆shell为/bin/bash的用户信息
[root@localhost ~]awk -F : 'BEGIN {print "USER USERID"} {print $1":"$3} END{print "end file"}' /etc/passwd 
[root@localhost ~]awk -F : '{print "USER USERID“;print $1":"$3} END{print "end file"}' /etc/passwd 
[root@localhost ~]awk -F: 'BEGIN{print "  USER  UID  \n--------------- "}{print $1,$3}' /etc/passwd 
[root@localhost ~]awk -F: 'BEGIN{print "  USER UID  \n--------------- "}{print $1,$3}'END{print "=============="} /etc/passwd 
[root@localhost ~]seq 10 |awk 'i=0'
#不打印内容
[root@localhost ~]seq 10 |awk 'i=1' 
#打印1至10
[root@localhost ~]seq 10 | awk 'i=!i' 
#由于i最开始没有复制, 默认为null 。输出奇数行
[root@localhost ~]seq 10 | awk '{i=!i;print i}'    
[root@localhost ~]seq 10 | awk '!(i=!i)'  
#输出偶数行
[root@localhost ~]seq 10 |awk -v i=1 'i=!i'
#输出偶数行
[root@localhost ~]seq 10 | sed -n '1~2p'
[root@localhost ~]seq 10 | sed -n '2~2p'
[root@localhost ~]df | awk -F "[ %]*" '/^\/dev\/sd/{if($(NF-2)>20)print $1,$5}'
#分隔符号同样可以实现正则表达式

5.2 Program:ACTION

  • 常用的action分类

    • Expressions : 算术,比较表达式等

    • Control statements : if, while等

    • Compound statements : 组合语句

    • input statements

    • output statements : print等

  • 常用控制语句

    • { statements;… } 组合语句

    • if(condition) {statements;…}

    • if(condition) {statements;…} else {statements;…}

    • while(conditon) {statments;…}

    • do {statements;…} while(condition)

    • for(expr1;expr2;expr3) {statements;…}

    • break

    • continue

    • delete array[index]

    • delete array

    • exit

5.2.1 控制语句if-else

  • 用法:

    • if(condition){statement;…}[else statement]

    • if(condition1){statement1}else if(condition2){statement2} else{statement3}

  • 使用场景 :对awk取得的整行或某个字段做条件判断

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"}}' 

5.2.2 控制语句 : while循环

  • 语法while(condition){statement;…}

  • 条件真,进入循环;条件假,退出循环 。

  • 使用场景

    • 对一行内的多个字段逐一类似处理时使用

    • 对数组中的各元素逐一处理时使用

  • 示例

    #内置函数length()返回字符数,而非字节数
    [root@centos8 ~]#awk 'BEGIN{print length("hello")}'
    5
    [root@centos8 ~]#awk 'BEGIN{print length("马哥教育")}'
    4
    [root@centos7 ~]#awk '/^[[:space:]]*linux16/{i=1;while(i<=NF){print 
    $i,length($i); i++}}' /etc/grub2.cfg
    linux16 7
    /vmlinuz-3.10.0-1062.el7.x86_64 31
    root=UUID=bebb9244-bbb8-4c69-9249-54a36c75155e 46
    ro 2
    crashkernel=auto 16
    rhgb 4
    quiet 5
    net.ifnames=0 13
    linux16 7
    /vmlinuz-0-rescue-b12558570741487c9328c996e3265b09 50
    root=UUID=bebb9244-bbb8-4c69-9249-54a36c75155e 46
    ro 2
    crashkernel=auto 16
    rhgb 4
    quiet 5
    net.ifnames=0 13
    
    [root@centos7 ~]#awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) 
    {if(length($i)>=10){print $i,length($i)}; i++}}' /etc/grub2.cfg
    /vmlinuz-3.10.0-1062.el7.x86_64 31
    root=UUID=bebb9244-bbb8-4c69-9249-54a36c75155e 46
    crashkernel=auto 16
    net.ifnames=0 13
    /vmlinuz-0-rescue-b12558570741487c9328c996e3265b09 50
    root=UUID=bebb9244-bbb8-4c69-9249-54a36c75155e 46
    crashkernel=auto 16
    net.ifnames=0 13
    
    [root@centos8 ~]#awk 'BEGIN{ total=0;i=1;while(i<=100){total+=i;i++};print 
    total}'
    5050

5.2.3 控制语句 : do-while循环

  • 语法do {statement;…}while(condition)

  • 意义:无论真假,至少执行一次循环体

  • 示例

    [root@centos8 ~]#awk 'BEGIN{ total=0;i=1;do{ total+=i;i++;}while(i<=100);print 
    total}'
    5050

5.2.4 控制语句 : for循环

  • 语法for(expr1;expr2;expr3) {statement;…}

  • 常见用法for(variable assignment;condition;iteration process) {for-body}

  • 特殊用法:能够遍历数组中的元素

    for(var in array) {for-body}

    范例 :

    [root@centos8 ~]#awk 'BEGIN{total=0;for(i=1;i<=100;i++){total+=i};print total}'
    5050
    
    [root@centos7 ~]#awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print 
    $i,length($i)}}' /etc/grub2.cfg
    linux16 7
    /vmlinuz-3.10.0-1062.el7.x86_64 31
    root=UUID=bebb9244-bbb8-4c69-9249-54a36c75155e 46
    ro 2
    crashkernel=auto 16
    rhgb 4
    quiet 5
    net.ifnames=0 13
    linux16 7
    /vmlinuz-0-rescue-b12558570741487c9328c996e3265b09 50
    root=UUID=bebb9244-bbb8-4c69-9249-54a36c75155e 46
    ro 2
    crashkernel=auto 16
    rhgb 4
    quiet 5
    net.ifnames=0 13

    性能比较

    [root@centos8 ~]# time (awk 'BEGIN{ total=0;for(i=0;i<=10000;i++){total+=i;};print total;}')
    50005000
    
    real	0m0.005s
    user	0m0.003s
    sys	0m0.002s
    [root@centos8 ~]# time (total=0;for i in {1..10000};do total=$(($total+i));done;echo $total)
    50005000
    
    real	0m0.055s
    user	0m0.052s
    sys	0m0.002s
    [root@centos8 ~]# time (for ((i=0;i<=10000;i++));do let total+=i;done;echo $total)
    50005000
    
    real	0m0.065s
    user	0m0.063s
    sys	0m0.000s
    [root@centos8 ~]# time (seq –s ”+” 10000|bc)
    seq: invalid floating point argument: ‘–s’
    Try 'seq --help' for more information.
    
    real	0m0.014s
    user	0m0.002s
    sys	0m0.009s

5.2.5 控制语句 : switch语句

  • 语法

    • switch(expression) {case VALUE1 or /REGEXP/: statement1; case VALUE2 or /REGEXP2/: statement2; ...; default: statementn}

5.2.6 控制语句 : break,continue 和next

  • break

    • 退出当前循环

    • break [n]

  • continue

    • 停止此次循环, 进入下一次的循环

    • continue [n]

  • next

    • 提前结束对本行处理而直接进入下一行处理(awk自身循环)

[root@centos8 ~]#awk 'BEGIN{sum=0;for(i=1;i<=100;i++)
{if(i%2==0)continue;sum+=i}print sum}'
2500

[root@centos8 ~]#awk 'BEGIN{sum=0;for(i=1;i<=100;i++)
{if(i==50)break;sum+=i}print sum}'
1225

[root@centos8 ~]#awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
root 0
daemon 2
lp 4
shutdown 6
mail 8
games 12
ftp 14
nobody 65534
polkitd 998
gluster 996
rtkit 172
rpc 32
chrony 994
saslauth 992
clevis 984
pegasus 66
colord 982
setroubleshoot 980
gdm 42
gnome-initial-setup 978
sshd 74
avahi 70
tcpdump 72
wang 1000

5.3 awk数组

awk默认使用的是关联数组:array[index-expression] ,在awk中使用关联数组不需要像shell中提前定义。

  • 格式 : array[index-expression]

  • 范例 : weekdays["mon"]="Monday"

  • index-expression :

    1. 可使用任意字符串;字符串要使用双引号括起来

    2. 如果某数组元素事先不存在,在引用时 , awk会自动创建 此元素,并将其值初始化为“空串”

    3. 若要判断数组中是否存在某元素,要使用index in array格式进行遍历

若要遍历数组中的每个元素,要使用for循环 for(var in array) {for-body} 注意:var会遍历array的每个索引

  • 示例

    [root@centos8 ~]#awk 
    'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";print 
    weekdays["mon"]}'
    Monday
    
    [root@centos8 ~]# cat dupfile 
    Sunday
    Monday 
    Tuesday 
    Tuesday 
    Wednesday
    [root@centos8 ~]# awk '!arr[$0]++' dupfile
    Sunday
    Monday 
    Tuesday 
    Wednesday
    [root@centos8 ~]# awk '{!line[$0]++;print $0, line[$0]}' dupfile
    Sunday 1
    Monday  1
    Tuesday  1
    Tuesday  2
    Wednesday 1
    awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]  ="Tuesday";for(i in weekdays) {print weekdays[i]}}'
    
    netstat -tan | awk '/^tcp/{state[$NF]++}END  {for(i in state) { print i,state[i]}}'
    
    awk '{ip[$1]++}END{for(i in ip) {print i,ip[i]}}' /var/log/httpd/access_log 
    
    awk '{ip[$1]++}END{for(i in ip)if(ip[i]>1000)print i}' /var/log/httpd/access_log |while read line ;do
    >iptables -A INPUT -s $line -j REJECT
    >done
    
    ss -nt | awk -F "[ :]+" '/ESTAB/{print $(NF-2)}' | sort | uniq -c | sort -nr | head -n3
    
    awk '{for(i=1;i<=NF;i++)word[$i]++}END{for(i in word)print word[i],i}' /etc/profile | sort -nr

5.4 awk函数

5.4.1 内置函数

  • 数值处理

    • rand() : 返回0和1之间一个随机数

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

rand() 函数使用时,需要与函数srand()一起使用时才会有产生随机数的效果,如果单纯的使用rand()函数,只会产生固定的值0.237788 使用awk 'BEGIN{srand();i=rand();print int(i*100)}'生成0-100之前的随机数。

  • 字符串处理

    • length([s]):返回指定字符串的长度

    • sub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并将第一个匹配的内容替换为s

      • echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'

    • gsub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并全部替换 为s所表示的内容

      • echo "2008:08:08 08:08:08" | awk ‘gsub(/:/,"-",$0)'

    • split(s,array,[r]):以r为分隔符,切割字符串s,并将切割后的结果保存 至array所表示的数组中,第一个索引值为1,第二个索引值为2,…

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

可以参考链接了解更多的awk内置函数:https://www.cnblogs.com/f-ck-need-u/p/7509812.html#9-awk-

5.4.2 自定义函数

自定义函数主要用于ACTION字段

  • 格式

function name ( parameter, parameter,  ... )
{           
            statements  
            return expression 
} 
  • 示例

cat fun.awk  
function max(v1,v2) {   
    v1>v2?var=v1:var=v2  
    return var 
}  
BEGIN{a=3;b=2;print max(a,b)}            

awk –f fun.awk  

5.5 awk中调用shell命令

awk中如果希望引用shell命令 , 需要使用system命令来调用对应命令。 ​ 空格是awk中的字符串连接符 , 如果system中需要使用awk中 的变量可以使用空格分隔 , 或者说除了awk的变量外其他一律 用""引用起来。

awk BEGIN'{system("hostname") }'   
awk 'BEGIN{score=100; system("echo  your score is " score) }' 

[root@localhost ~]# awk -v string=1 BEGIN'{system("echo"  string  ) }' 
sh: echo1: command not found
[root@localhost ~]# awk -v string=1 BEGIN'{system("echo "  string  ) }' 
1
# 需要注意的时, 如果echo后面没有空格,运行命令时默认会将echo 与string变量合并成一个值,即echo1 认为会错误的。所以使用echo命令需要在后面加空格

[root@localhost ~]# ss -nt |awk -F "[ :]+" '/^ESATB/{IP[$(NF-2)]++}END{for( i in IP ){if(IP[i] > 3 )system("REJECT")}}'

6. awk脚本

awk程序写成脚本 , 直接调用或执行 需要注意的是 , 如果直接写成awk脚本 , 需要将shebang写成#/bin/awk -f

  • 示例

cat f1.awk 
{if($3>=1000)print $1,$3}    

awk -F: -f f1.awk /etc/passwd  
---------------------------------------------------------------------- 
cat f2.awk   
#!/bin/awk –f  
#this is a awk script  
{if($3>=1000)print $1,$3}   

chmod +x f2.awk   

f2.awk –F:  /etc/passwd 

6.1 向awk脚本传递参数

awk很重要且必备的能力是接受外界的变量 , 例如shell中的变量 , shell中命令执行的结果 , 或者是在开始执行awk前应该初始化的变量。 ​ 例如 , 在shell中定义一个变量name , 传递给awk使用。

awk -v awk_name="$name" 'BEGIN{print awk_name}' 
Ma longshuai
  • 有三种方式可以向awk传递变量:

    • 直接使用赋值语句

    • 使用-v选项传递

    • 通过参数数组`ARGV``的方式

6.1.1 第一种:直接使用赋值语句

  将待传递变量当作文件名被awk解析。awk识别后发现是赋值语句 , 就认为其是变量传递。变量赋值语句必须定义awk program之后。此法定义的变量不可在BEGIN中使用 , 因为它是被当成文件解析的 , 只有在需要读取主输入文件的时候才会被解析。

awk 'BEGIN{}PATTERN{print var1,var2,var3}' var1=value1 var2=value2 file1 var3=value3 var1=value4 file2

在上面的语句中 , 当awk执行完BEGIN程序后 , 准备读取主输入 , 于是开始解析program后的输入文件。解析时发现 , var1var2都是赋值语句 , 于是当成变量处理 , 当读取到file1时 , 发现只有一个参数 , 则当作输入文件 , 于是开始处理该文件。在处理file1时 , var1var2都是有效的 , 但var3还未赋值 , 因此var3无效。当处理完file1后 , 继续解析下一个主输入文件 , 此时var3被赋值 , 并开始处理file2。在处理file2时 , var1var2var3都是有效的 , 但var1被新值覆盖。 ​ 同样,可以将前面PROGRAM部分写进文件中 。

  • 格式awkfile var=value var2=value2... Inputfile

注意:在BEGIN过程中不可用。直到首行输入完成以后,变 量才可用。可以通过-v 参数,让awk在执行BEGIN之前得到 变量的值。命令行中每一个指定的变量都需要一个-v参数


[root@localhost ~]# cat  test.awk      
#!/bin/awk –f  
{if($3 >=min && $3<=max)print $1,$3}   

[root@localhost ~]# chmod +x test.awk  
[root@localhost ~]# test.awk -F: min=100 max=200  /etc/passwd 

此外 , 还可以将shell命令的结果赋值给这些预定义变量。如下展示了几种变量定义的方式:

[root@localhost ~]#name="Ma longshuai"

[root@localhost ~]# awk  '{ print var1,var2,var3}' OFS=":" var1="$name" var2="`echo Ma longshuai2`" var3="Ma longshuai3" var4=Malongshuai4 /root/filename
Ma longshuai:Ma longshuai2:Ma longshuai3

[root@localhost ~]# awk  '{ print var1,var2,var3}' OFS=":" var1=$name var2="`echo Ma longshuai2`" var3="Ma longshuai3" var4=Malongshuai4 /root/filename
Ma::
Ma:Ma longshuai2:Ma longshuai3

不仅可以定义普通变量 , 还可以定义内置变量(如上OFS)。注意加引号的方式 : 为了安全,应该对所有赋值语句的value部分加上双引号 , 除非所赋的值不包含特殊字符。所以 , 如果上面的var1赋值语句写成var1=$name ,将被awk解析成var1=Ma longshuai,于是var1的值为Ma , 主输入文件为longshuai

6.1.2 第二种 : 使用-v选项传递

使用-v选项传递。变量赋值语句必须定义在awk program之前。这种方法定义的变量可以在BEGIN程序中使用。 ​ 除了定义在program之前,定义方式同上。每定义一个变量,都需要使用一个-v选项。如:

name="Ma longshuai"
awk -v OFS=":" -v var1="$name" -v var2="`echo Ma longshuai2`" -v var3="Ma longshuai3" 'program' filename

6.1.3 第三种 : 通过参数数组ARGV的方式。

ARGV是内置的数组变量。awk内部会将命令行切分 , 并按规则将各参数存放到ARGV数组中 , 数组下标从0开始 , 这是awk中唯一下标从0开始的数组。在存放到ARGV时 ,所有的选项和program会被忽略。 ​ 每存储一个数组变量,特殊变量ARGC的值增加1。因此ARGC的值代表的是参数的个数。所以 , 数组变量从ARGV[0]ARGV[ARGC-1]。 可使用类似下面的循环来遍历ARGV数组。

[root@localhost ~]# awk -F "\t" -v var1="value1" 'BEGIN{
>         for(i=0;i<ARGC;++i){
>             print "ARGV[" i "]: " ARGV[i]
>         }
>         print "ARGC: " ARGC
>     }' "a" "b" "v=1" file 
ARGV[0]: awk
ARGV[1]: a
ARGV[2]: b
ARGV[3]: v=1
ARGV[4]: file
ARGC: 5
​

注意,ARGV[0]存储的是awk命令 , -F-v选项都没有存储到ARGV中。

  ARGCARGV数组变量的值都可以手动修改。命令行分割存储完成之后 , 开始处理BEGIN , 再处理主循环输入。因此,在BEGIN中修改ARGV中输入文件对应的值 , 可以改变awk所读取的输入文件 , 若将其设置为空 , 则该数组变量直接被跳过 , 也就不再读取该输入文件。

  需要注意的是 , 当增加ARGV元素时 , 必须同时递增ARGC的值 , 因为awk是根据AGRC来读取ARGV的。同理 , 只增加ARGC的值 , 将导致新建ARGV数组元素 , 且这些新元素的值为空。也因此 , 如果减小ARGC的值 , 将导致无法访问超出ARGC-1边界的ARGV元素。


参考链接 :

博客园:ggjucheng:linux awk命令详解

博客园:金马俊龙:awk知识总结


熊熊