十二、 awk文本处理
引言:awk是功能强大的编辑工具它是专门为文本处理设计的编程语言,也是处理软件,通常用于扫描、过滤、统计汇总工作,数据可以来自标准输入也可以是管道或文件。grep,sed,awk 更是Shell 编程中经常用到的文本处理工具, 被称之为Shell 编程三剑客。
一、awk介绍
1. awk概述
awk是一种编程语言,主要用于在linux/unix下对文本和数据进行处理,是linux/unix下的一个工具。数据可以来自标准输入、一个或多个文件,或其它命令的输出。
awk的处理文本和数据的方式:逐行扫描文件,默认从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。
awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。
gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。
下面介绍的awk是以GNU的gawk为例的,在linux系统中已把awk链接到gawk,所以下面全部以awk进行介绍。
2. awk能干啥?
awk用来处理文件和数据的,是类unix下的一个工具,也是一种编程语言
可以用来统计数据,比如网站的访问量,访问的IP量等等
支持条件判断,支持for和while循环
3.命令格式
awk [option] 'pattern[action]' file ...
awk [选项参数] 'script' var=value file(s)
awk [选项参数] -f scriptfile var=value file(s)
awk 参数 条件动作 文件
action 是指动作,awk擅长文本格式化,且能输出格式化后的结果,因此最常用的动作就是 print 和 printf
4.awk参数
-F 指定分隔字段符 -v 定义或修改一个awk内部变量 -f 从脚本文件中读取awk命令
5.awk处理文本内容模式
awk默认以空格为分隔符,且多个空格也识别为一个空格,作为分隔符;
awk按行处理文件,一行处理完毕之后,再处理下一行;
awk可以根据用户指定的分隔符去工作,没有指定,则默认为空格;
二、awk 工具原理
当读到第一行时,匹配条件,然后执行指定动作,再接着读取第二行数据处理,不会默认输出
如果没有定义匹配条件默认是匹配所有数据行,awk隐含循环,条件匹配多少次动作就会执行多少次
逐行读取文本,默认以空格为分隔符进行分隔,将分隔所得的各个字段保存到内建变量中,并按模式或者条件执行编辑命令
sed 与awk的区别
sed命令常用于一整行的处理,而awk比较、倾向于将一行分成多个""字段"然后再进行处理。awk信息的读入也是逐行读取的,执行结果可以通过print的功能将字段数据打印显示。
工作原理
解析:
1.awk使用一行作为输入,并将这一行赋给内部变量$0,每一行也可称为一个记录,以换行符(RS)结束
2.每行被间隔符**:**(默认为空格或制表符)分解成字段(或域),每个字段存储在已编号的变量中,从$1开始
问:awk如何知道用空格来分隔字段的呢?
答:因为有一个内部变量FS来确定字段分隔符。初始时,FS赋为空格
3.awk使用print函数打印字段,打印出来的字段会以空格分隔,因为$1,$3之间有一个逗号。逗号比较特殊,它映射为另一个内部变量,称为输出字段分隔符OFS,OFS默认为空格
4.awk处理完一行后,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕
三、awk内置变量
awk 包含几个特殊的内建变量(可直接用)如下所示:
FS 指定每行文本的字段分隔符,缺省为空格或制表位
NF 当前处理的行的字段个数
NR 当前处理的行的行号(序数)
$0 当前处理的行的整行内容
$n 当前处理行的第n个字段(第n列)
四、awk用法示例
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表示当前行被分割后一共有几列。
通过管道、双引号调用shell 命令
[root@linux2 ~]# cat /etc/passwd | head -10 > test.txt [root@linux2 ~]# cat test.txt 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin
如图:
打印文本内容
[root@linux2 ~]# awk '{print}' test.txt 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin
如图:
以: 为分隔符,打印第一列
[root@linux2 ~]# awk -F: '{print $1}' test.txt root bin daemon adm lp sync shutdown halt mail operator
如图:
也可以以x为分隔符,打印第一列
[root@linux2 ~]# awk -Fx '{print $1}' test.txt root: bin: daemon: adm: lp: sync: shutdown: halt: mail: operator:
如图:
以冒号为分隔符,打印第1列和第3列{$1,$3}有空格效果也可以" " 也会有空格
[root@linux2 ~]# awk -F: '{print $1 $3}' test.txt
root0
bin1
daemon2
adm3
lp4
sync5
shutdown6
halt7
mail8
operator11
[root@linux2 ~]# awk -F: '{print $1" "$3}' test.txt root 0 bin 1 daemon 2 adm 3 lp 4 sync 5 shutdown 6 halt 7 mail 8 operator 11 [root@linux2 ~]# awk -F: '{print $1,$3}' test.txt root 0 bin 1 daemon 2 adm 3 lp 4 sync 5 shutdown 6 halt 7 mail 8 operator 11
如图:
制表符隔开
[root@linux2 ~]# awk -F: '{print $1"\t"$3}' test.txt
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
如图:
定义多个分隔符,只要出现一个字符就可以作为分隔符 以 :和/ 为分隔符
[root@linux2 ~]# awk -F[:/] '{print $9}' test.txt bin sbin sbin lpd bin sbin sbin mail sbin
如图:
查询本机的网卡IP,与使用流量
[root@linux2 ~]# ifconfig ens33 ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.198.128 netmask 255.255.255.0 broadcast 192.168.198.255 inet6 fe80::b58:d8cf:925f:afa0 prefixlen 64 scopeid 0x20<link> ether 00:0c:29:f3:71:3c txqueuelen 1000 (Ethernet) RX packets 21901 bytes 2951294 (2.8 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 33091 bytes 2523428 (2.4 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 [root@linux2 ~]# ifconfig ens33 | awk '/netmask/{print "本机IP地址为 " $2}' 本机IP地址为 192.168.198.128 [root@linux2 ~]# ifconfig ens33 | awk '/RX p/{print $5"字节"}' 2978269字节
如图:
1、BEGIN、END的用法
使用模式pattern。先介绍两种特殊模式,BEGIN END。BGEIN模式指定处理文本之前需要执行的操作,END模式表示处理完所有行之后执行的操作。
逐行执行开始之前执行什么任务,结束之后再执行什么任务,用BEGIN、END
BEGIN:一般用来做初始化操作,仅在读取数据记录之前执行一次
END:一般用来做汇总操作,仅在读取完数据记录之后执行一次
典型的 awk 程序包含下面三个区域:
1. BEGIN 区域
BEGIN { awk-commands }
BEGIN 区域的命令只最开始、在 awk 执行 body 区域命令之前执行一次。
BEGIN 区域很适合用来打印报文头部信息,以及用来初始化变量。
BEGIN 区域可以有一个或多个 awk 命令
关键字 BEGIN 必须要用大写
BEGIN 区域是可选的
2.body 区域
body 区域的语法:
/pattern/ {action}
body 区域的命令每次从输入文件读取一行就会执行一次
如果输入文件有 10 行,那 body 区域的命令就会执行 10 次(每行执行一次)
Body 区域没有用任何关键字表示,只有用正则模式和命令。
3. END block
END 区域的语法:
END { awk-commands }
END 区域在 awk 执行完所有操作后执行,并且只执行一次。
END 区域很适合打印报文结尾信息,以及做一些清理动作
END 区域可以有一个或多个 awk 命令
关键字 END 必须要用大写
END 区域是可选的
BEGIN的运算案例:
定义变量打印输出结果
[root@linux2 ~]# awk 'BEGIN{x=10;print x}' 10 [root@linux2 ~]# awk 'BEGIN{x=10;print x+1}' 11 [root@linux2 ~]# awk 'BEGIN{x=10;x++;print x}' 11 [root@linux2 ~]#
不定义变量,初始值为0,如果是字符串为空
[root@linux2 ~]# awk 'BEGIN{print x}' [root@linux2 ~]# awk 'BEGIN{print x+1}' 1 [root@linux2 ~]# awk 'BEGIN{print x+1+1}' 2
2**3和2^3 为2的3次方
[root@linux2 ~]# awk 'BEGIN{print 2.5+1.5}' 4 [root@linux2 ~]# awk 'BEGIN{print 2**3}' 8 [root@linux2 ~]# awk 'BEGIN{print 2^3}' 8 [root@linux2 ~]# awk 'BEGIN{print 1/2}' 0.5
例子
+ - * / %(模) ^(幂2^3) 可以在模式中执行计算,awk都将按浮点数方式执行算术运算 # awk 'BEGIN{print 1+1}' # awk 'BEGIN{print 1**1}' # awk 'BEGIN{print 2**3}' # awk 'BEGIN{print 2/3}'
定义引用变量
2、awk内置变量
awk变量分为内置变量和自定义变量。以下列出awk常用的内置变量
$1 代表第一列 $2 代表第二列以此类推 $0 代表整行 NF 一行的列数;字段个数 (读取的列数) NR 行数;记录数(行号)从1开始,新的文件延续上面的计数,新文件不从1开始 FNR 读取文件的记录数(行号)从1开始,新的文件重新从1开始计数 FS 输入字段的分隔符 ,默认为空格 OFS 输出的字段分隔符 默认为空格 RS 输入行分隔符,默认为换行符 ORS 输出行分隔符,默认为换行符
FILENAME 当前文件名
ARGC 命令行参数个数
ARGV 数组,保存的是命令行所给定的各个参数
1)$0代表整整行
打印包含root的行 $0 整行 $1:第一列
[root@linux2 ~]# awk -F: '/root/{print $0}' test.txt root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin [root@linux2 ~]# awk -F: '/root/{print $1}' test.txt root operator [root@linux2 ~]# awk -F: '/root/{print $1,$6}' test.txt root /root operator /root
如图:
2)NF代表一行的列数
打印以:和/ 为分隔符且包含root的行的列数
[root@linux2 ~]# awk -F[:/] '/root/{print NF}' test.txt 10 10 [root@linux2 ~]# awk -F[:] '/root/{print NF}' test.txt 7 7 [root@linux2 ~]#
如图:
打印最后一列和倒数第二列(登录shell和家目录)
[root@linux2 ~]# cat test.txt 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin [root@linux2 ~]# [root@linux2 ~]# awk 'BEGIN{ FS=":";print "Login_shell\tLogin_home\n*******************"};{print $NF"\t"$(NF-1)};END{print "************************"}' test.txt Login_shell Login_home ******************* /bin/bash /root /sbin/nologin /bin /sbin/nologin /sbin /sbin/nologin /var/adm /sbin/nologin /var/spool/lpd /bin/sync /sbin /sbin/shutdown /sbin /sbin/halt /sbin /sbin/nologin /var/spool/mail /sbin/nologin /root ************************
[root@linux2 ~]# awk -F: 'BEGIN{ print "Login_shell\t\tLogin_home\n*******************"};{print $NF"\t\t"$(NF-1)};END{print "************************"}' test.txt
Login_shell Login_home
*******************
/bin/bash /root
/sbin/nologin /bin
/sbin/nologin /sbin
/sbin/nologin /var/adm
/sbin/nologin /var/spool/lpd
/bin/sync /sbin
/sbin/shutdown /sbin
/sbin/halt /sbin
/sbin/nologin /var/spool/mail
/sbin/nologin /root
************************
如图:
3)NR表示每一行的行号
打印以:和/ 为分隔符 包含root的行数
[root@linux2 ~]# awk -F[:/] '/root/{print NR}' test.txt 1 10
如图:
打印以:为分隔符包含root的行数和整行内容
[root@linux2 ~]# awk -F: '/root/{print NR,$0}' test.txt 1 root:x:0:0:root:/root:/bin/bash 10 operator:x:11:0:operator:/root:/sbin/nologin
如图:
打印第二行 打印第二行以:为分隔符打印第一列
[root@linux2 ~]# awk -F: 'NR==2' test.txt bin:x:1:1:bin:/bin:/sbin/nologin [root@linux2 ~]# awk -F: 'NR==2{print $1}' test.txt bin
如图:
打印最后一列
[root@linux2 ~]# awk -F: '{print $NF}' test.txt /bin/bash /sbin/nologin /sbin/nologin /sbin/nologin /sbin/nologin /bin/sync /sbin/shutdown /sbin/halt /sbin/nologin /sbin/nologin
如图:
打印显示行号和整行内容
[root@linux2 ~]# awk -F: '{print NR,$0}' test.txt 1 root:x:0:0:root:/root:/bin/bash 2 bin:x:1:1:bin:/bin:/sbin/nologin 3 daemon:x:2:2:daemon:/sbin:/sbin/nologin 4 adm:x:3:4:adm:/var/adm:/sbin/nologin 5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin 6 sync:x:5:0:sync:/sbin:/bin/sync 7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown 8 halt:x:7:0:halt:/sbin:/sbin/halt 9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin 10 operator:x:11:0:operator:/root:/sbin/nologin
如图:
打印显示汇总行数以及打印汇总最后一行内容
[root@linux2 ~]# awk -F: END'{print NR}' test.txt 10 [root@linux2 ~]# awk -F: END'{print $0}' test.txt operator:x:11:0:operator:/root:/sbin/nologin
如图:
以:为分隔符打印文本内容有多少行和多少列
[root@linux2 ~]# awk -F: '{print "第"NR"行有"NF"列"}' test.txt 第1行有7列 第2行有7列 第3行有7列 第4行有7列 第5行有7列 第6行有7列 第7行有7列 第8行有7列 第9行有7列 第10行有7列
如图:
显示文件第一列,倒是第一列,和倒数第二列的内容
[root@linux2 ~]# awk -F: '{print $1,$(NF-1),$(NF-2)}' test.txt
root /root root
bin /bin bin
daemon /sbin daemon
adm /var/adm adm
lp /var/spool/lpd lp
sync /sbin sync
shutdown /sbin shutdown
halt /sbin halt
mail /var/spool/mail mail
operator /root operator
如图:
取出密码文件中的第一列和最后一列
考察对自定义输入分隔符的使用,可以看到,下面的文本文件中,可以考虑使用 : 进行分割;
[root@linux2 ~]# cat test.txt 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin
命令如下:
[root@linux2 ~]# awk -F ':' '{print $1,$NF}' test.txt root /bin/bash bin /sbin/nologin daemon /sbin/nologin adm /sbin/nologin lp /sbin/nologin sync /bin/sync shutdown /sbin/shutdown halt /sbin/halt mail /sbin/nologin operator /sbin/nologin
截图:
显示磁盘可用空间
打印出第二行的第四列
[root@linux2 ~]# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 1.9G 0 1.9G 0% /dev tmpfs 1.9G 0 1.9G 0% /dev/shm tmpfs 1.9G 13M 1.9G 1% /run tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup /dev/mapper/centos-root 47G 12G 36G 25% / /dev/sda1 1014M 195M 820M 20% /boot overlay 47G 12G 36G 25% /var/lib/docker/overlay2/e4f475d222b430b3ba12c0fc99083100a0e2018bede3f915a173e2283992e030/merged overlay 47G 12G 36G 25% /var/lib/docker/overlay2/22b8495fa4d17d2416a636188a1c13b2524893e7728269bf8f5e8e6ff9d2165e/merged overlay 47G 12G 36G 25% /var/lib/docker/overlay2/2cc10369becb5956aa5b5a4d31b13aa8b0df5e4248a492a7d2e4b9998cd305de/merged overlay 47G 12G 36G 25% /var/lib/docker/overlay2/5a4c170bdbbf91fb3304619104964705ee49c0a667761b50db65008e0484858f/merged overlay 47G 12G 36G 25% /var/lib/docker/overlay2/c30d1feb3265a5fed5e1b9112659158a6fd2404f363b673fa20148d23c7f7ad3/merged overlay 47G 12G 36G 25% /var/lib/docker/overlay2/3a4179461352caf1bb85faef8290ef27fa0b4f596c01ee20f918168a82e91add/merged tmpfs 378M 0 378M 0% /run/user/0 [root@linux2 ~]# df -h | awk 'NR==2{print $4}' 1.9G
[root@linux2 ~]# awk -F: '($1~"root") && (NF==7) {print $1,$2,$7}' /etc/passwd root x /bin/bash
如图:
4)FS和OFS
FS 输入字段的分隔符 ,默认为空格
OFS 输出的字段分隔符 默认为空格
我们知道awk命令执行后,默认采用空格分割字段,而这个空格就是默认的输出分割符,
单在某些情况下,为了将数据展示的效果更加醒目一些,就可以使用OFS的自定义输出分隔符;
仍然以上面的密码文本为例,输出第一列和最后一列的字段;
[root@linux2 ~]# cat test.txt 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin
[root@linux2 ~]# awk -F ':' -v OFS=' *** ' '{print $1,$NF}' test.txt root *** /bin/bash bin *** /sbin/nologin daemon *** /sbin/nologin adm *** /sbin/nologin lp *** /sbin/nologin sync *** /bin/sync shutdown *** /sbin/shutdown halt *** /sbin/halt mail *** /sbin/nologin operator *** /sbin/nologin
该表默认输出分隔符,直接在awk后面使用: -v OFS='自定义输出分隔符'
案例
[root@linux2 ~]# awk 'BEGIN{FS=":"}{print $1}' /etc/passwd root bin daemon adm lp sync shutdown halt mail operator games ftp nobody systemd-network dbus polkitd sshd postfix es ntp user1 user2 user3 user4 user5 user6 user7 user8 user9 user10 zhangsan lisi wangwu zhaoliu nginx
如图:
OFS定义输出时以:为分隔符,$1,$2用,分开 OFS输出时定义分隔符------ 打印出第一列和第二列
[root@linux2 ~]# awk 'BEGIN{FS=":";OFS="------"}{print $1,$2}' /etc/passwd root------x bin------x daemon------x adm------x lp------x sync------x shutdown------x halt------x mail------x operator------x games------x ftp------x nobody------x systemd-network------x dbus------x polkitd------x sshd------x postfix------x es------x ntp------x
如图:
5)FNR
内置变量FNR,awk处理多个文件时,如果使用NR显示行号,那么多个文件的所有行会按照顺序进行排序。如果想要分别显示两个文件的行号,这时候要用到内置变量FNR
FNR和NR的区别 FNR读取文件的行号,从1开始新文件也是从1开始 打印出2个文件的内容
[root@linux2 ~]# awk '{print FNR,$0}' /etc/resolv.conf /etc/hosts 1 # Generated by NetworkManager 2 search localdomain 3 nameserver 192.168.198.2 1 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 2 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 3 192.168.198.130 linux1 4 192.168.198.131 linux2 5 192.168.197.132 linux3 6
6)RS
内置变量RS,RS是行分隔符。如果不指定,行分隔符就是默认的回车符。如果不想使用默认的回车换行,想使用空格作为换行符。可以理解为每遇到一个空格就换一行
以:为换行符,打印整行内容
[root@linux2 ~]# awk 'BEGIN{RS=":"}{print $0}' /etc/passwd root x 0 0 root /root /bin/bash bin x 1 1 bin /bin /sbin/nologin daemon x
如图:
7)ORS
内置变量ORS,输出行分隔符。默认也是回车符作为输出行分隔符。
以空格为分隔符,输出为多行合并,默认为回车键
[root@linux2 ~]# awk 'BEGIN{ORS=" "}{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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin polkitd:x:999:998:User for polkitd:/:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin es:x:1000:1000::/home/es:/bin/bash ntp:x:38:38::/etc/ntp:/sbin/nologin user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash user4:x:1004:1004::/home/user4:/bin/bash user5:x:1005:1005::/home/user5:/bin/bash user6:x:1006:1006::/home/user6:/bin/bash user7:x:1007:1007::/home/user7:/bin/bash user8:x:1008:1008::/home/user8:/bin/bash user9:x:1009:1009::/home/user9:/bin/bash user10:x:1010:1010::/home/user10:/bin/bash zhangsan:x:1011:1011::/home/zhangsan:/bin/bash lisi:x:1012:1012::/home/lisi:/bin/bash wangwu:x:1013:1013::/home/wangwu:/bin/bash zhaoliu:x:1014:1014::/home/zhaoliu:/bin/bash nginx:x:998:994:Nginx web server:/var/lib/nginx:/sbin/nologin [root@linux2 ~]#
如图:
8)FILENAME 使用
内置变量FILENAME,就是显示文件名
FILENAME 为awk的内置变量,通过下面这个命令,可以看到在每行记录之前,输出了当前文件名称;
root@linux2 ~]# awk 'NR==1,NR==3{print FILENAME,$0}' test.txt
test.txt root:x:0:0:root:/root:/bin/bash
test.txt bin:x:1:1:bin:/bin:/sbin/nologin
test.txt daemon:x:2:2:daemon:/sbin:/sbin/nologin
root@linux2 ~]# awk 'NR==1,NR==3{print FILENAME,$0}' test.txt test.txt root:x:0:0:root:/root:/bin/bash test.txt bin:x:1:1:bin:/bin:/sbin/nologin test.txt daemon:x:2:2:daemon:/sbin:/sbin/nologin
9)内置变量ARGC和ARGV
先来看下面这条命令的执行结果
[root@linux2 ~]# awk 'NR==1,NR==3{print ARGV[0],ARGV[1],$0}' test.txt awk test.txt root:x:0:0:root:/root:/bin/bash awk test.txt bin:x:1:1:bin:/bin:/sbin/nologin awk test.txt daemon:x:2:2:daemon:/sbin:/sbin/nologin
可以发现,在输出的每一行记录前面,拼上了 awk 和 test.txt这两个字段,这两个字段就是这行命令整体解析出来的2个内置参数;
ARGV内置变量表示一个数组,这个数组中保存命令行给定的参数,示例如下:
[root@linux2 ~]# awk 'BEGIN{print "aaa"}' test test1 aaa [root@linux2 ~]# 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@linux2 ~]# awk 'BEGIN{print "aaa", ARGV[0], ARGV[1], ARGV[2], ARGC}' test test1 aaa awk test test1 3
如图:
自定义变量
就是用户定义的变量。有两种方法。
方法一:-v varname=value 变量名区分大小写
方法二:在program中直接定义
通过方法一自定义变量,与设置内置变量的方法是一样的。当我们需要在awk中引用shell中的变量时,可以通过方法一间接引用
1、自定义变量
[root@linux2 ~]# awk -v myVar="testVar" 'BEGIN{print myVar}' testVar
2、引用shell中变量 [root@linux2 ~]# abc=6666666 [root@linux2 ~]# awk -v myVar=$abc 'BEGIN{print myVar}' 6666666
使用方法二定义,直接在程序中定义即可。变量定义和动作之间要用分号隔开,还可以定义多个变量
[root@linux2 ~]# awk 'BEGIN{myvar="ttt"; print myvar}' ttt [root@linux2 ~]# awk 'BEGIN{myvar1="aaa"; myvar2="bbb"; print myvar1, myvar2}' aaa bbb
3、awk格式化输出
在上文,我们接触的是awk的输出功能,主要使用了 print 这个进行输出,它只能对文本进行简单的输出,但是并不能美化或者修改输出格式;
如果需要改变文本的格式,就需要printf命令。关于printf命令的使用方法请参考(https://www.cnblogs.com/jkin/p/10758802.html)
print函数 类似echo "hello world" # date |awk '{print "Month: "$2 "\nYear: "$NF}' # awk -F: '{print "username is: " $1 "\t uid is: "$3}' /etc/passwd printf函数 类似echo -n # awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' /etc/passwd # awk -F: '{printf "|%15s| %10s| %15s|\n", $1,$2,$3}' /etc/passwd # awk -F: '{printf "|%-15s| %-10s| %-15s|\n", $1,$2,$3}' /etc/passwd awk 'BEGIN{FS=":"};{printf "%-15s %-15s %-15s\n",$1,$6,$NF}' a.txt %s 字符类型 strings %-20s %d 数值类型 占15字符 - 表示左对齐,默认是右对齐 printf默认不会在行尾自动换行,加\n
如下,假如我们直接使用 printf 这样操作,看下效果
[root@linux2 ~]# awk '{printf $0}' test.txt root:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinadm:x:3:4:adm:/var/adm:/sbin/nologinlp:x:4:7:lp:/var/spool/lpd:/sbin/nologinsync:x:5:0:sync:/sbin:/bin/syncshutdown:x:6:0:shutdown:/sbin:/sbin/shutdownhalt:x:7:0:halt:/sbin:/sbin/haltmail:x:8:12:mail:/var/spool/mail:/sbin/nologinoperator:x:11:0:operator:/root:/sbin/nologin[root@linux2 ~]#
如图:
明显来说,把所有内容都输出到同一行了,这时候,就需要使用 printf的格式化输出来控制;
[root@linux2 ~]# awk '{printf "%s\n", $0}' test.txt 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin
如图:
再看一个案例,使用 printf 将文本中的每一列添加前置输出
[root@linux2 ~]# awk -F: '{printf "第一列:%s 第二列:%s 第三列:%s\n" ,$1,$2,$3}' test.txt 第一列:root 第二列:x 第三列:0 第一列:bin 第二列:x 第三列:1 第一列:daemon 第二列:x 第三列:2 第一列:adm 第二列:x 第三列:3 第一列:lp 第二列:x 第三列:4 第一列:sync 第二列:x 第三列:5 第一列:shutdown 第二列:x 第三列:6 第一列:halt 第二列:x 第三列:7 第一列:mail 第二列:x 第三列:8 第一列:operator 第二列:x 第三列:11
如图:
利用awk的printf动作,即可对文本进行格式化输出。下边我们用一个简单的例子,看一下print动作和printf动作的区别
[root@linux2 ~]# cat test2.txt aaa bbb 123 cdf swe ef4 fw2 fer4 fve [root@linux2 ~]# [root@linux2 ~]# awk '{print $1}' test2.txt aaa swe [root@linux2 ~]# awk '{printf $1}' test2.txt
aaaswe[root@linux2 ~]#
由例子可以看出,printf动作和printf命令一样,都不输出换行符,默认输出文本在一行里面。但是printf动作既然和printf命令用法一样,printf动作肯定有格式替换符了。我们就可以用格式替换符来替换一下$1了,示例如下:
[root@linux2 ~]# awk '{printf "%s\n", $1}' test2.txt aaa swe
看起来printf动作和printf命令使用方法是一样的,但是仔细看会发现,printf动作和printf还是存在唯一的不同点。在使用printf动作时,指定的格式与列($1)之间需要使用逗号隔开。在使用printf命令时,指定的格式和传入的文本不需要使用逗号隔开。示例如下所示:
[root@linux2 ~]# awk '{printf "%s\n", $1}' test2.txt aaa swe [root@linux2 ~]# printf "%s\n" aaa swe aaa swe
其实他们还有一些不同之处,在使用printf命令时,当指定的格式中只有一个格式替换符,但是传入了多个参数,那么这个参数可以重复使用这一个格式替换符。但是在awk里边不能这么用,在awk中,格式替换符的数量必须与传入参数的数量相同。也就是说,格式替换符必须与需要格式化的参数一一对应。示例如下:
[root@linux2 ~]# printf "%s\n" 1 2 3 4 1 2 3 4 [root@linux2 ~]# awk 'BEGIN{printf "%s\n", 1, 2, 3, 4}' 1 [root@linux2 ~]# awk 'BEGIN{printf "%s\n%s\n%s\n%s\n", 1, 2, 3, 4}' 1 2 3 4
总结,在awk中使用printf动作时,需要注意以下几点
(1)、使用printf动作输出的文本不会换行,如果需要换行,可以在对应的格式替换符后边加 \n 进行转义
(2)、使用printf动作时,指定的格式与被格式化的文本之间,需要用逗号隔开
(3)、使用printf动作时,格式中的格式替换符必须与被格式的文本一一对应
练手小例子:
我们可以利用格式替换符对文本中的每一列进行格式化:
[root@linux2 ~]# cat test2.txt aaa bbb 123 cdf swe ef4 fw2 fer4 fve [root@linux2 ~]# awk '{printf "第一列:%s 第二列:%s\n", $1, $2}' test2.txt 第一列:aaa 第二列:bbb 第一列:swe 第二列:ef4
上述例子完美的展现了awk对文本格式化的能力。awk本身负责文本切割,printf负责文本格式化。
我们还可以利用awk的begin模式,结合printf动作,输出一个表格
[root@linux2 ~]# awk -v FS=":" 'BEGIN{printf "%-10s\t %s\n", "用户名称","用户ID"} {printf "%-10s\t %s\n", $1,$3}' /etc/passwd 用户名称 用户ID root 0 bin 1 daemon 2 adm 3 lp 4 sync 5 shutdown 6 halt 7 mail 8 operator 11
举例说明
打印/etc/passwd里的用户名、家目录及登录shell
[root@linux2 ~]# cat test.txt 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin [root@linux2 ~]# awk -F: 'BEGIN{print "u_name\t\th_dir\t\tshell" RS "*****************"} {printf "%-15s %-20s %-20s\n",$1,$(NF-1),$NF}END{print "***************************"}' test.txt u_name h_dir shell ***************** root /root /bin/bash bin /bin /sbin/nologin daemon /sbin /sbin/nologin adm /var/adm /sbin/nologin lp /var/spool/lpd /sbin/nologin sync /sbin /bin/sync shutdown /sbin /sbin/shutdown halt /sbin /sbin/halt mail /var/spool/mail /sbin/nologin operator /root /sbin/nologin ***************************
如图:
4.awk模式解析
在刚开始介绍awk命令的时候,我们已经介绍了两种模式BEGIN和END。此处,我们将详细的介绍以下awk中的模式。模式这个词听上去不容易被理解,我们这里换一个说法,把模式换成条件。我们知道awk时逐行处理文本的,也就是说,awk处理完当前行,再处理下一行。如果我们不指定任何条件,awk会逐行处理文本中的每一行。但是如果我们指定了条件,只要满足条件的行会被处理,不满足条件的行不会被处理。这就是awk中的模式。
当awk处理文本时,会把pattern(模式)作为条件,判断将要被处理的行是否满足条件。是否能跟模式进行匹配,如果匹配则进行处理,不匹配不进行处理。我们通过一个例子来理解:
文本test1有三行,第一行有4列,第二行有5列,第三行有2列。
[root@linux2 ~]# cat test2.txt
aaa bbb 123 cdf
swe ef4 fw2 fer4 fve
111 222
[root@linux2 ~]# awk 'NF==5 {print $0}' test2.txt
swe ef4 fw2 fer4 fve
以上例子使用了一个简单的模式,也可以理解为我们使用了一个条件。这个条件就是如果被处理的行刚好是5列。那被处理的行就满足条件,满足条件的行会执行相应的动作。即打印当前行,只要第二行有5列,所以输出了第二行。举一反三:
[root@linux2 ~]# cat test2.txt aaa bbb 123 cdf swe ef4 fw2 fer4 fve 111 222 [root@linux2 ~]# awk 'NF>2 {print $0}' test2.txt aaa bbb 123 cdf swe ef4 fw2 fer4 fve [root@linux2 ~]# awk 'NF<=4 {print $0}' test2.txt aaa bbb 123 cdf 111 222 [root@linux2 ~]# awk '$1=="aaa" {print $0}' test2.txt aaa bbb 123 cdf [root@linux2 ~]#
上面的模式都有一个共同点,就是在上述模式中,都使用了关系运算符。当经过关系运算得出得结果为真时,则满足条件,然后执行相应得动作。我们总结一下awk支持的关系运算符。
关系运算符 | 含义 | 用法示例 |
< | 小于 | x < y |
<= | 小于等于 | x <= y |
== | 等于 | x == y |
!= | 不等于 | x != y |
>= | 大于等于 | x >= y |
> | 大于 | x > y |
~ | 与对应正则匹配则为真 | x ~/正则/ |
!~ | 与对应正则不匹配则为真 | x !~/正则/ |
我们把用到了关系运算符的模式称之为:关系表达式模式或者关系运算符模式。
模糊匹配
用~表示包含,!~表示不包含
打印出第一列包含root和ro的行
[root@linux2 ~]# awk -F: '$1~/root/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@linux2 ~]#
[root@linux2 ~]# awk -F: '$1~/ro/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
打印显示不包含结尾是nologin,打印出第一列和第7列
[root@linux2 ~]# awk -F: '$7!~/nologin$/{print $1,$7}' /etc/passwd
root /bin/bash
sync /bin/sync
shutdown /sbin/shutdown
halt /sbin/halt
es /bin/bash
user1 /bin/bash
user2 /bin/bash
user3 /bin/bash
user4 /bin/bash
user5 /bin/bash
user6 /bin/bash
user7 /bin/bash
user8 /bin/bash
user9 /bin/bash
user10 /bin/bash
zhangsan /bin/bash
lisi /bin/bash
wangwu /bin/bash
zhaoliu /bin/bash
如图:
数值与字符串的比较
比较符号:== != <= >= < >
打印显示第五行
[root@linux2 ~]# awk -F: 'NR==5{print}' /etc/passwd lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
打印显示小于五行的内容
[root@linux2 ~]# awk -F: 'NR<5' /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
以:为分隔符打印显示第三列为0 的内容
[root@linux2 ~]# awk -F: '$3==0' /etc/passwd root:x:0:0:root:/root:/bin/bash
精确查找,$1 第一列一定是root的行显示
[root@linux2 ~]# awk -F: '$1=="root"' test.txt root:x:0:0:root:/root:/bin/bash [root@linux2 ~]# awk -F: '$1=="root"{print}' test.txt root:x:0:0:root:/root:/bin/bash
如图:
查询第3列大于1000的内容并显示(其中1000以上的值表示普通用户)
[root@linux2 ~]# awk -F: '$3>1000{print}' /etc/passwd user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash user4:x:1004:1004::/home/user4:/bin/bash user5:x:1005:1005::/home/user5:/bin/bash user6:x:1006:1006::/home/user6:/bin/bash user7:x:1007:1007::/home/user7:/bin/bash user8:x:1008:1008::/home/user8:/bin/bash user9:x:1009:1009::/home/user9:/bin/bash user10:x:1010:1010::/home/user10:/bin/bash zhangsan:x:1011:1011::/home/zhangsan:/bin/bash lisi:x:1012:1012::/home/lisi:/bin/bash wangwu:x:1013:1013::/home/wangwu:/bin/bash zhaoliu:x:1014:1014::/home/zhaoliu:/bin/bash
如图:
逻辑运算&&和||
||:或 表示同时满足
&&:且 满足一个即可
打印显示第三列小于10或第三列大于等于1000的行
[root@linux2 ~]# awk -F: '$3<10 || $3>=1000' /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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin es:x:1000:1000::/home/es:/bin/bash user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash user4:x:1004:1004::/home/user4:/bin/bash user5:x:1005:1005::/home/user5:/bin/bash user6:x:1006:1006::/home/user6:/bin/bash user7:x:1007:1007::/home/user7:/bin/bash user8:x:1008:1008::/home/user8:/bin/bash user9:x:1009:1009::/home/user9:/bin/bash user10:x:1010:1010::/home/user10:/bin/bash zhangsan:x:1011:1011::/home/zhangsan:/bin/bash lisi:x:1012:1012::/home/lisi:/bin/bash wangwu:x:1013:1013::/home/wangwu:/bin/bash zhaoliu:x:1014:1014::/home/zhaoliu:/bin/bash
如图:
打印显示大于4行且小于10行的内容 也就是5-9行的内容
[root@linux2 ~]# awk -F: 'NR>4 && NR<10' /etc/passwd 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
如图:
输出数字1~200,打印显示第一列能被7整除的数且包含数字7
[root@linux2 ~]# seq 200 | awk '$1%7==0 && $1~/7/' 7 70 77 147 175
其实在学习模式之前,我们一直都在使用模式。之前我们没有指定模式的时候,其实也是一种模式,称之为空模式。空模式会匹配文本中的每一行。即每一行都满足条件。所以每一行都会执行相应的动作。目前为止我们已经接触了空模式、关系运算模式、BEGIN/END模式、这三种。
正则模式
正则模式,顾名思义,正则模式和正则表达式有关。正则模式可以理解为,把正则表达式当条件。能与正则匹配的行,就算满足条件。满足条件的行才会执行相应的动作。不能被正则匹配的行,则不会执行相应的动作。我们通过一个例子来理解:
[root@linux2 ~]# grep "^ro" /etc/passwd root:x:0:0:root:/root:/bin/bash [root@linux2 ~]# awk '/^ro/{print $0}' /etc/passwd root:x:0:0:root:/root:/bin/bash
如图:
如上例所示,我们通过grep命令配合正则表达式找到了我们所需要的信息。同样的通过awk命令一样找到了我们所需要的信息,grep命令和awk命令使用了相同的正则表达式"^ro" 。 唯一的区别时,grep命令直接使用正则表达式,而在awk命令中,正则表达式被放在了两个斜线中。在上例中,grep看起来更简单一些,但是awk的优势在于它强大的格式化能力。
[root@linux2 ~]# awk -v FS=":" 'BEGIN{printf "%-10s%10s\n","用户名","用户ID"} /^s/{printf "%-15s%10s\n", $1,$3}' /etc/passwd 用户名 用户ID sync 5 shutdown 6 systemd-network 192 sshd 74
如图:
从这个例子可以看出,该例子使用BGEIN模式生成了表头,使用正则模式把/etc/passwd文件中以字母s打头的用户名和用户ID筛选出来。使用awk一条命令完成了多项工作。
需要注意的是,在使用正则模式时,如果正则中包含"/",则需要进行转义,我们通过一个例子来理解。
[root@linux2 ~]# grep "/bin/bash$" /etc/passwd root:x:0:0:root:/root:/bin/bash es:x:1000:1000::/home/es:/bin/bash user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash user4:x:1004:1004::/home/user4:/bin/bash user5:x:1005:1005::/home/user5:/bin/bash user6:x:1006:1006::/home/user6:/bin/bash user7:x:1007:1007::/home/user7:/bin/bash user8:x:1008:1008::/home/user8:/bin/bash user9:x:1009:1009::/home/user9:/bin/bash user10:x:1010:1010::/home/user10:/bin/bash zhangsan:x:1011:1011::/home/zhangsan:/bin/bash lisi:x:1012:1012::/home/lisi:/bin/bash wangwu:x:1013:1013::/home/wangwu:/bin/bash zhaoliu:x:1014:1014::/home/zhaoliu:/bin/bash
[root@linux2 ~]# awk '/\/bin\/bash$/{print $0}' /etc/passwd root:x:0:0:root:/root:/bin/bash es:x:1000:1000::/home/es:/bin/bash user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash user4:x:1004:1004::/home/user4:/bin/bash user5:x:1005:1005::/home/user5:/bin/bash user6:x:1006:1006::/home/user6:/bin/bash user7:x:1007:1007::/home/user7:/bin/bash user8:x:1008:1008::/home/user8:/bin/bash user9:x:1009:1009::/home/user9:/bin/bash user10:x:1010:1010::/home/user10:/bin/bash zhangsan:x:1011:1011::/home/zhangsan:/bin/bash lisi:x:1012:1012::/home/lisi:/bin/bash wangwu:x:1013:1013::/home/wangwu:/bin/bash zhaoliu:x:1014:1014::/home/zhaoliu:/bin/bash
如图:
如上所示,使用/bin/bash作为shell的用户被我们找了出来。但是因为正则中包含 / 。awk使用正则模式时,又需要把正则放到两个 / 中,所以需要转义。初次之外,还有两点需要注意:
(1)在awk命令中使用正则模式时,使用到的正则用法属于"扩展正则表达式"。
(2)当使用{x,y}这种次数匹配的正则表达式时,需要配合--posix或者--re-interval选项。(centos7貌似目前没有这个注意事项了)
行范围模式
上文中介绍了正则模式,理解了正则模式。再理解行范围模式,就容易多了。我们通过一个问题来引出行范围模式。
假设一个文本,内容如下:
[root@linux2 ~]# cat test3.txt Allen Phillips Green Lee William Aiden James Lee Angel Jack Tyler Kevin Lucas Thomas Kevin [root@linux2 ~]# cat -n test3.txt 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@linux2 ~]# awk '/Lee/,/Kevin/{print $0}' test3.txt 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@linux2 ~]# awk 'NR>3 && NR<6 {print $0}' test3.txt Angel Jack Tyler Kevin
如图:
其他
在上文讲关系表达式模式时,有一个关系运算符的表格。有一个关系运算符需要和正则配合,它就是 ~ 。我们通过一个例子来理解一下 ~ 和 !~ 运算符。
[root@linux2 ~]# cat test4.txt
主机名 网卡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@linux2 ~]#
[root@linux2 ~]# awk '$2~/192\.168\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1,$2}' test4.txt
主机A 192.168.2.123
主机B 192.168.2.222
主机F 192.168.1.234
如图:
上述示例需求是在test4.txt文本中找出,网卡1的IP地址在192.168网段的主机。$2为内置变量,表示文本中第二列。"$2~/正则/"表示文本中第二列如果和正则匹配。则执行对应的动作。
到目前为止,我们已经认识了awk的模式,模式可以分为以下五种
(1)、空模式 (2)、关系运算模式 (3)、正则模式 (4)、行范围模式 (5)、BEGIN/END模式
举例说明:
从第一行开始匹配到以lp开头行 awk -F: 'NR==1,/^lp/{print $0 }' passwd 从第一行到第5行 awk -F: 'NR==1,NR==5{print $0 }' passwd 从以lp开头的行匹配到第10行 awk -F: '/^lp/,NR==10{print $0 }' passwd 从以root开头的行匹配到以lp开头的行 awk -F: '/^root/,/^lp/{print $0}' passwd 打印以root开头或者以lp开头的行 awk -F: '/^root/ || /^lp/{print $0}' passwd awk -F: '/^root/;/^lp/{print $0}' passwd 显示5-10行 awk -F':' 'NR>=5 && NR<=10 {print $0}' /etc/passwd awk -F: 'NR<10 && NR>5 {print $0}' passwd 打印30-39行以bash结尾的内容: [root@MissHou shell06]# awk 'NR>=30 && NR<=39 && $0 ~ /bash$/{print $0}' passwd stu1:x:500:500::/home/stu1:/bin/bash yunwei:x:501:501::/home/yunwei:/bin/bash user01:x:502:502::/home/user01:/bin/bash user02:x:503:503::/home/user02:/bin/bash user03:x:504:504::/home/user03:/bin/bash [root@MissHou shell06]# awk 'NR>=3 && NR<=8 && /bash$/' 1.txt stu7:x:1007:1007::/rhome/stu7:/bin/bash stu8:x:1008:1008::/rhome/stu8:/bin/bash stu9:x:1009:1009::/rhome/stu9:/bin/bash 打印文件中1-5并且以root开头的行 [root@MissHou shell06]# awk 'NR>=1 && NR<=5 && $0 ~ /^root/{print $0}' 1.txt root:x:0:0:root:/root:/bin/bash [root@MissHou shell06]# awk 'NR>=1 && NR<=5 && $0 !~ /^root/{print $0}' 1.txt 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 理解;号和||的含义: [root@MissHou shell06]# awk 'NR>=3 && NR<=8 || /bash$/' 1.txt [root@MissHou shell06]# awk 'NR>=3 && NR<=8;/bash$/' 1.txt 打印IP地址 # ifconfig eth0|awk 'NR>1 {print $2}'|awk -F':' 'NR<2 {print $2}' # ifconfig eth0|grep Bcast|awk -F':' '{print $2}'|awk '{print $1}' # ifconfig eth0|grep Bcast|awk '{print $2}'|awk -F: '{print $2}' # ifconfig eth0|awk NR==2|awk -F '[ :]+' '{print $4RS$6RS$8}' # ifconfig eth0|awk -F"[ :]+" '/inet addr:/{print $4}'
练习
1.显示可以登录操作系统的用户所有信息 从第7列匹配以bash结尾,输出整行(当前行所有的列)
[root@MissHou ~] awk '/bash$/{print $0}' /etc/passwd
[root@MissHou ~] awk '/bash$/{print $0}' /etc/passwd
[root@MissHou ~] awk '/bash$/' /etc/passwd
[root@MissHou ~] awk -F: '$7 ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$NF ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$0 ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$0 ~ /\/bin\/bash/' /etc/passwd
2.显示可以登录系统的用户名
awk -F: '$0 ~ /\/bin\/bash/{print $1}' /etc/passwd
3.打印出系统中普通用户的UID和用户名
500 stu1
501 yunwei
502 user01
503 user02
504 user03
# awk -F: 'BEGIN{print "UID\tUSERNAME"} {if($3>=500 && $3 !=65534 ) {print $3"\t"$1} }' /etc/passwdUID USERNAME
# awk -F: '{if($3 >= 500 && $3 != 65534) print $1,$3}' a.txt
redhat 508
user01 509
u01 510
YUNWEI 511
5、awk动作解析
我们从一个小例子来分析动作, awk '{print $0}' file
如上例所示, '{print $0}' 就是我们所说的动作,想必大家一定很熟悉了。该动作的最外层是括号 {} ,内层是print $0。之前我们一直把它当成一个动作。现在我们把它拆开来理解。其实,被查开这两部分都可以称之为动作,只不过它们属于不同类型的动作而已。
print 属于输出语句类型的动作。顾名思义,输出语句类型动作的作用就是输出,打印信息。print和printf都属于输出语句类型的动作。
{}也被称为动作。 {}属于组合语句类型的动作,组合语句类型的动作就是将多个代码组合成代码块。我们通过一个示例来理解下:
[root@linux2 ~]# cat test5.txt f s 1 2 2 1 [root@linux2 ~]# awk '{print $1} {print $2}' test5.txt f s 1 2 2 1 [root@linux2 ~]# awk '{print $1; print $2}' test5.txt f s 1 2 2 1
如图:
如上所示,当我们使用了两个大括号。他们属于组合类型的动作,分别将两个print括住,表示这两个print动作分别是独立的个体。也就是说,一共有四个动作,两个括号和两个print。每个大括号中只有一个动作。但是组合语句动作的作用是将多个代码块组合成一个整体,如上第三条命令所示,只使用了一个大括号,就可以将两个print动作组合成一个整体。每段动作之间需要用分号隔开。
控制语句
除了输出动作和组合语句动作之外。当然还有其他动作,现在我们认识一下另一种动作,它就是控制语句。第一个控制语句就是条件判断。也就是编程语法中的if判断语句。
if [ xxx ];then
xxx
fi
在awk中,同样可以使用if语句进行条件判断。只不过在命令行的awk中,我们要将上例中的多行语句写在一行中。
awk 选项 '正则,地址定位{awk语句}' 文件名 { if(表达式){语句1;语句2;...}}
例子:
[root@linux2 ~]# cat test.txt
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
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
[root@linux2 ~]# awk -F: '{if($3==0) {print $1"是管理员"} }' test.txt root是管理员 [root@linux2 ~]# awk 'BEGIN{if('$(id -u)'==0) {print "admin"} }' test.txt admin [root@linux2 ~]# awk -F: '{if($3>=500 && $3<=60000) {print $1,$3} }' /etc/passwd polkitd 999 es 1000 user1 1001 user2 1002 user3 1003 user4 1004 user5 1005 user6 1006 user7 1007 user8 1008 user9 1009 user10 1010 zhangsan 1011 lisi 1012 wangwu 1013 zhaoliu 1014 nginx 998
如图:
示例如下:
[root@linux2 ~]# awk '{if(NR==1){print $0}}' test5.txt
f s
如图:
上例中即为条件判断的语法。if(NR==1),NR是内置变量表示行号,所以,该条件表示的是当行号为1时,条件成立。即该动作表示的是打印文本的第一行。
如上例,为什么最外层需要一个大括号?没有原因,必须这么写,否则报错。可以这么理解,所以的动作最外层必须用大括号 {} 括起来。那么if的语法结构里边也包含大括号。if里边的大括号是否属于组合语句?我们可以这么理解,if语句仍然属于控制语句,控制语句中包含组合语句。if语句的大括号中,也可以执行多个动作,把多个代码段当成一个整体。也就是说,如果if条件成立,则执行if大括号里的所有动作,示例如下:
[root@linux2 ~]# awk '{if(NR==1){print $1; print $2}}' test5.txt f s
如图:
上例中,如果行号为1,则满足条件。就会执行if对应大括号的所有代码。两个print动作一起执行,如果不成立,两个动作都不执行。该例中,if对应的大括号里边有多条语句。所有if语法中的大括号不能省略。如果if对应的大括号里边只有一条命令,那么if对应的大括号可以省略。
[root@linux2 ~]# awk '{if(NR==1)print $0}' test5.txt f s
如图:
如上所示,如果if对应的大括号里边只有一条命令,那么if对应的大括号可以省略。如果条件成立,需要执行多条语句,大括号不可以省略。上例还可以通过模式NR==1来实现,虽然语法不同,但是结果是一样的。
编程语言中,除了if判断,还有if...else..语法和if...else if...else...语法
if...else语法
if [ xxx ];then
xxxxx
else
xxx
fi
格式:
{if(表达式){语句;语句;...}else{语句;语句;...}}
例如:
[root@linux2 ~]# awk -F: '{ if($3>=500 && $3 != 65534) {print $1"是普通用户"} else {print $1,"不是普通用户"}}' /etc/passwd root 不是普通用户 bin 不是普通用户 daemon 不是普通用户 adm 不是普通用户 lp 不是普通用户 sync 不是普通用户 shutdown 不是普通用户 halt 不是普通用户 mail 不是普通用户 operator 不是普通用户 games 不是普通用户 ftp 不是普通用户 nobody 不是普通用户 systemd-network 不是普通用户
[root@linux2 ~]# awk 'BEGIN{if( '$(id -u)'>=500 && '$(id -u)' !=65534 ) {print "是普通用户"} else {print "不是普通用户"}}' /etc/passwd
不是普通用户
如图:
if...elif...else结构:
if [xxxx];then
xxxx
elif [xxx];then
xxx
....
else
...
fi
格式:
{ if(表达式1){语句;语句;...}else if(表达式2){语句;语句;...}else if(表达式3){语句;语句;...}else{语句;语句;...}}
其实,这些语法在编程语言中都是相同的。我们直接来看示例:
在 /etc/passwd文件的第三列存在的是用户ID。在centos6中,ID小于500的是系统用户,大于500的是普通用户。centos7中,ID小于1000的是系统用户,大于1000的是普通用户。我们以centos7为例,用awk找出哪些是系统用户,哪些是普通用户。
[root@linux2 ~]# 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
如图:
我们再看一下 if...else if...else的例子
[root@linux2 ~]# cat test6.txt 姓名 年龄 苍井空 32 吉泽明步 28 樱井莉亚 18 [root@linux2 ~]# awk 'NR!=1 {if($2<20){print $1,"年轻人"}else if($2<30){print $1,"中年人"}else{print($1,"老年人")}}' test6.txt 苍井空 老年人 吉泽明步 中年人 樱井莉亚 年轻人
如图:
例子:
awk -F: '{ if($3==0) {print $1,":是管理员"} else if($3>=1 && $3<=499 || $3==65534 ) {print $1,":是系统用户"} else {print $1,":是普通用户"}}' awk -F: '{ if($3==0) {i++} else if($3>=1 && $3<=499 || $3==65534 ) {j++} else {k++}};END{print "管理员个数为:"i "\n系统用户个数为:"j"\n普通用户的个数为:"k }' # awk -F: '{if($3==0) {print $1,"is admin"} else if($3>=1 && $3<=499 || $3==65534) {print $1,"is sys users"} else {print $1,"is general user"} }' a.txt awk -F: '{ if($3==0) {print $1":管理员"} else if($3>=1 && $3<500 || $3==65534 ) {print $1":是系统用户"} else {print $1":是普通用户"}}' /etc/passwd awk -F: '{if($3==0) {i++} else if($3>=1 && $3<500 || $3==65534){j++} else {k++}};END{print "管理员个数为:" i RS "系统用户个数为:"j RS "普通用户的个数为:"k }' /etc/passwd 管理员个数为:1 系统用户个数为:28 普通用户的个数为:27 # awk -F: '{ if($3==0) {print $1":是管理员"} else if($3>=500 && $3!=65534) {print $1":是普通用户"} else {print $1":是系统用户"}}' passwd awk -F: '{if($3==0){i++} else if($3>=500){k++} else{j++}} END{print i; print k; print j}' /etc/passwd awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员个数: "i; print "普通用个数: "k; print "系统用户: "j}' /etc/passwd 如果是普通用户打印默认shell,如果是系统用户打印用户名 # awk -F: '{if($3>=1 && $3<500 || $3 == 65534) {print $1} else if($3>=500 && $3<=60000 ) {print $NF} }' /etc/passwd
效果如下:
[root@linux2 ~]# cat test.txt 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 halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin
[root@linux2 ~]# awk -F: '{ if($3==0) {print $1,":是管理员"} else if($3>=1 && $3<=499 || $3==65534 ) {print $1,":是系统用户"} else {print $1,":是普通用户"}}' test.txt root :是管理员 bin :是系统用户 daemon :是系统用户 adm :是系统用户 lp :是系统用户 sync :是系统用户 shutdown :是系统用户 halt :是系统用户 mail :是系统用户 operator :是系统用户
[root@linux2 ~]# awk -F: '{ if($3==0) {i++} else if($3>=1 && $3<=499 || $3==65534 ) {j++} else {k++}};END{print "管理员个数为:"i "\n系统用户个数为:"j"\n普通用户的个 数为:"k }' /etc/passwd
管理员个数为:1
系统用户个数为:17
普通用户的个数为:17
[root@linux2 ~]# awk -F: '{if($3==0) {print $1,"is admin"} else if($3>=1 && $3<=499 || $3==65534) {print $1,"is sys users"} else {print $1,"is general user"} }' test.txt
root is admin
bin is sys users
daemon is sys users
adm is sys users
lp is sys users
sync is sys users
shutdown is sys users
halt is sys users
mail is sys users
operator is sys users
[root@linux2 ~]# awk -F: '{ if($3==0) {print $1":管理员"} else if($3>=1 && $3<500 || $3==65534 ) {print $1":是系统用户"} else {print $1":是普通用户"}}' /etc/passwd
root:管理员
bin:是系统用户
daemon:是系统用户
adm:是系统用户
lp:是系统用户
sync:是系统用户
shutdown:是系统用户
halt:是系统用户
mail:是系统用户
operator:是系统用户
games:是系统用户
ftp:是系统用户
nobody:是系统用户
systemd-network:是系统用户
dbus:是系统用户
polkitd:是普通用户
sshd:是系统用户
postfix:是系统用户
es:是普通用户
ntp:是系统用户
user1:是普通用户
user2:是普通用户
user3:是普通用户
user4:是普通用户
user5:是普通用户
user6:是普通用户
user7:是普通用户
user8:是普通用户
user9:是普通用户
user10:是普通用户
zhangsan:是普通用户
lisi:是普通用户
wangwu:是普通用户
zhaoliu:是普通用户
nginx:是普通用户
[root@linux2 ~]# awk -F: '{if($3==0) {i++} else if($3>=1 && $3<500 || $3==65534){j++} else {k++}};END{print "管理员个数为:" i RS "系统用户个数为:"j RS "普通用户的个 数为:"k }' /etc/passwd
管理员个数为:1
系统用户个数为:17
普通用户的个数为:17
[root@linux2 ~]# awk -F: '{ if($3==0) {print $1":是管理员"} else if($3>=500 && $3!=65534) {print $1":是普通用户"} else {print $1":是系统用户"}}' /etc/passwd
root:是管理员
bin:是系统用户
daemon:是系统用户
adm:是系统用户
lp:是系统用户
sync:是系统用户
shutdown:是系统用户
halt:是系统用户
mail:是系统用户
operator:是系统用户
games:是系统用户
ftp:是系统用户
nobody:是系统用户
systemd-network:是系统用户
dbus:是系统用户
polkitd:是普通用户
sshd:是系统用户
postfix:是系统用户
es:是普通用户
ntp:是系统用户
user1:是普通用户
[root@linux2 ~]# awk -F: '{if($3==0){i++} else if($3>=500){k++} else{j++}} END{print i; print k; print j}' /etc/passwd
1
17
17
[root@linux2 ~]#
[root@linux2 ~]# awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员个数: "i; print "普通用个数: "k; print "系统用户: "j}' /etc/passwd
管理员个数: 1
普通用个数: 15
系统用户: 19
[root@linux2 ~]# awk -F: '{if($3>=1 && $3<500 || $3 == 65534) {print $1} else if($3>=500 && $3<=60000 ) {print $NF} }' /etc/passwd
bin
daemon
adm
lp
sync
shutdown
halt
operator
games
ftp
nobody
systemd-network
dbus
/sbin/nologin
sshd
postfix
/bin/bash
ntp
/bin/bash
/bin/bash
/bin/bash
上文我们介绍了控制语句中的条件判断。除了条件判断,控制语句还包括循环。awk中也有for循环和while循环,我们先来看一下循环语句的语法:
#for循环语法格式1 for(初始化; 布尔表达式; 更新) { //代码语句 } #for循环语法格式2 for(变量 in 数组) { //代码语句 } #while循环语法 while( 布尔表达式 ) { //代码语句 } #do...while循环语法 do { //代码语句 }while(条件)
现在我们通过一个示例来了解一下循环语句,因为还没有介绍数组,我们先演示上述语法中格式一的语法:
[root@linux2 ~]# awk 'BEGIN{for(i=1;i<=3;i++){print i}}' 1 2 3
上例中,我们使用了BEGIN模式,BEGIN模式对应的动作中,包含了for循环语句。和其他语言中的for循环几乎没什么区别,只不过写在了一行而已。
for循环例子:
打印1~5 for ((i=1;i<=5;i++));do echo $i;done # awk 'BEGIN { for(i=1;i<=5;i++) {print i} }' 打印1~10中的奇数 # for ((i=1;i<=10;i+=2));do echo $i;done|awk '{sum+=$0};END{print sum}' # awk 'BEGIN{ for(i=1;i<=10;i+=2) {print i} }' # awk 'BEGIN{ for(i=1;i<=10;i+=2) print i }' 计算1-5的和 # awk 'BEGIN{sum=0;for(i=1;i<=5;i++) sum+=i;print sum}' # awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);{print sum}}' # awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);print sum}'
再来看一下while的具体使用,为了方便演示,仍然使用BEGIN模式。示例如下:
[root@linux2 ~]# awk -v i=1 'BEGIN{while(i<=3){print i;i++}}' 1 2 3 [root@linux2 ~]# awk 'BEGIN{i=1;while(i<=3){print i; i++}}' 1 2 3
如图:
当while对应的条件满足时,则执行对应的语句。语句执行完成后,对条件进行修改。
while循环例子:
打印1-5 # i=1;while (($i<=5));do echo $i;let i++;done # awk 'BEGIN { i=1;while(i<=5) {print i;i++} }' 打印1~10中的奇数 # awk 'BEGIN{i=1;while(i<=10) {print i;i+=2} }' 计算1-5的和 # awk 'BEGIN{i=1;sum=0;while(i<=5) {sum+=i;i++}; print sum }' # awk 'BEGIN {i=1;while(i<=5) {(sum+=i) i++};print sum }'
同理,do...while的示例如下,它与while循环的不同之处在于。while循环当满足条件时才会执行对应语句,而do...while无论条件是否满足,都会先执行一遍do里边的代码。然后再判断是否满足while中对应的条件,满足条件,则执行do对应的代码。如果不满足条件,则不再执行do对应的代码。
[root@linux2 ~]# awk 'BEGIN{i=1;do{print "test"; i++}while(i<1)}' test [root@linux2 ~]# awk 'BEGIN{do{print "test"; i++}while(i<=3)}' test test test test [root@linux2 ~]#
如图:
如上所示,无论是否满足while中的条件,都会先执行一遍do对应的代码。
嵌套循环
嵌套循环: #!/bin/bash for ((y=1;y<=5;y++)) do for ((x=1;x<=$y;x++)) do echo -n $x done echo done awk 'BEGIN{ for(y=1;y<=5;y++) {for(x=1;x<=y;x++) {printf x} ;print } }' # awk 'BEGIN { for(y=1;y<=5;y++) { for(x=1;x<=y;x++) {printf x};print} }' 1 12 123 1234 12345 # awk 'BEGIN{ y=1;while(y<=5) { for(x=1;x<=y;x++) {printf x};y++;print}}' 1 12 123 1234 12345 尝试用三种方法打印99口诀表: #awk 'BEGIN{for(y=1;y<=9;y++) { for(x=1;x<=y;x++) {printf x"*"y"="x*y"\t"};print} }' #awk 'BEGIN{for(y=1;y<=9;y++) { for(x=1;x<=y;x++) printf x"*"y"="x*y"\t";print} }' #awk 'BEGIN{i=1;while(i<=9){for(j=1;j<=i;j++) {printf j"*"i"="j*i"\t"};print;i++ }}' #awk 'BEGIN{for(i=1;i<=9;i++){j=1;while(j<=i) {printf j"*"i"="i*j"\t";j++};print}}'
提到了循环,就必须说说跳出循环的语句。和其他编程语言一样,在awk中,同样使用break和continue跳出循环。
break 条件满足的时候中断循环
continue 条件满足的时候跳过循环
continue的作用,跳出当前循环
break的作用,跳出整个循环
我们先来看一个continue的例子,示例如下:
[root@linux2 ~]# awk 'BEGIN{for(i=1;i<6;i++){print i}}' 1 2 3 4 5 [root@linux2 ~]# [root@linux2 ~]# awk 'BEGIN{for(i=1;i<6;i++){if(i==3){continue};print i}}' 1 2 4 5
如图:
由于在for循环中添加了条件判断,所以当i等于3时。跳过了当前本次循环。没有执行当前循环需要打印的动作,所以上例中数字3没有被打印出来。如果想结束的风彻底,可以用break结束循环:
[root@linux2 ~]# 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@linux2 ~]# awk 'BEGIN{print 1; print 2; print 3}' 1 2 3 [root@linux2 ~]# awk 'BEGIN{print 1; exit; print 2; print 3}' 1 [root@linux2 ~]#
如上例所示,在第一条命令中,执行了多个动作。在第二条命令中,也执行了多条动作。但当在awk中执行了exit语句时,之后的动作就不再执行了,想当于退出了awk命令。
其实这样描述exit并不完全准确,因为,当在AWK中使用了END模式后,exit的作用并不是退出awk命令,而是直接执行END模式中的动作,示例如下:
[root@linux2 ~]# cat test7.txt 1 2 3 [root@linux2 ~]# awk 'BEGIN{print "start"}{print $0}END{print "over"}' test7.txt start 1 2 3 over [root@linux2 ~]# awk 'BEGIN{print "start"; exit}{print $0}END{print "over"}' test7.txt start over
如图:
如上例所示,在awk命令中使用了END模式后。如果执行了exit语句,那么exit语句之后的动作就不再执行。直接执行END模式中的动作。
在awk中,除了可以使用exit命令结束awk命令。还可以使用next命令结束当前行,什么意思呢?在前边我们提到,awk时逐行处理文本的。awk会处理完当前行在处理下一行。那么,当awk处理的某行时,我们可以告诉awk这一行不用处理了,直接处理下一行。这时候就可以使用next命令来实现这个需求。换句话说,next命令可以使awk不对当前行执行对应的动作。而是直接处理下一行。示例如下:
[root@linux2 ~]# awk '{print $0}' test7.txt 1 2 3 [root@linux2 ~]# awk '{if(NR==2){next};print $0}' test7.txt 1 3
其实,next和continue有点类似,只是continue是针对循环而言的,next是针对逐行处理而言的。
到此,awk常用的流程控制语句与循环语句都已经总结完毕了。
6.awk数组详解
在其他编程语言中,都有数组的概念。我们可以通过数组的下标,引用数组中的元素。在其他语言中,通常数组的下标都是从0开始。awk也是支持数组的。但是在awk中,数组的下标是从1开始的。但是为了兼容使用习惯。我们也可以从0开始设置下标,到后边你就会明白,我们先来看一个示例。在其他语言中,一般都需要先声明一个数组。但是在awk中不需要声明,直接给数组中得元素赋值即可。示例如下:
[root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; print huluwa[1]}' 二娃
如图:
如上例所示,在BEGIN模式中,存了一个数组。放置了三个元素。想引用第二个元素的值,只要引用下标为1的元素即可。当前我们可以在数组中放置更多的元素。如果命令太长,可能回影响阅读性。我们可以使用Linux命令行的换行符进行换行,Linux的命令换行符为反斜线 \ 。
[root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]="六娃"; print huluwa[5]}' 六娃 [root@linux2 ~]# [root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]=""; print huluwa[5]}' [root@linux2 ~]#
如图:
上例中第二条命令,六娃的本来是隐身。我们把第六个元素设置为空字符串来代表隐身。当打印第六个元素时,打印出的值就是空(注:空格不为空)。举这个例子,是为了说明在awk中,元素的值可以设置为空,在awk中将元素的值设置为空是合法的。
既然awk中的元素可以为空,那么就不可以根据元素的值是否为空去判断该元素是否存在了。所以你通过以下办法来判断一个元素是否存在是不正确的,示例如下:
[root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]=""; if(huluwa[5]==""){print "第六个元素不存在"}}' 第六个元素不存在
如图:
上例中,第六个元素是存在的,但是通过上述方法判断元素是否存在时,显示是不存在。
其实,通过空字符串判断一个元素是否存在之所以不合理,除了上述原因之外。还有一个原因,就是当一个元素不存在与数组时,如果我们直接引用这个不存在的元素。awk会自动创建这个元素,并且默认这个元素为空字符串。示例如下:
[root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]=""; print huluwa[6]}' [root@linux2 ~]# [root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]=""; if(huluwa[6]==""){print "第七个元素不存在"}}' 第七个元素不存在 [root@linux2 ~]#
如图:
如上例第一个命令所示,数组中没有第七个元素。但是当我们输出第七个元素时,输出了空。那么,我们应该如何判断一个元素在数组中是否存在呢。我们可以使用这样的语法 if(下标 in 数组名) 。从而判断数组中是否存在对应的元素。
[root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]=""; if(5 in huluwa){print "第六个元素存在"}}' 第六个元素存在 [root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]=""; if(6 in huluwa){print "第七个元素存在"}}' [root@linux2 ~]#
如图:
当然我们还可以使用 ! 对条件进行取反,awk中数组的下标不仅可以是数字,还可以是任意字符串。如果使用过shell中的数组,你可以把awk的数组比作shell中的关联数组。
[root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]=""; if(!(6 in huluwa)){print "第七个元素不存在"}}' 第七个元素不存在 [root@linux2 ~]# [root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa["wuwa"]="五娃"; huluwa[5]=""; print huluwa["wuwa"]}' 五娃 [root@linux2 ~]#
如图:
其实,awk本身就是关联数组。最开始以数字最为下标,是因为数字容易理解。awk默认会把数字下标转换为字符串。所以,它本质上还是一个使用字符串为下标的关联数组。
使用delete可以删除数组中的元素,也可以删除整个数组,示例如下:
[root@linux2 ~]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ > huluwa[4]="五娃"; huluwa[5]="六娃"; print huluwa[4]; delete huluwa[4]; print huluwa[4]}' 五娃 [root@linux2 ~]# 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@linux2 ~]#
如图:
到目前为止,我们已经介绍了怎么给数组中的元素赋值,输出某个元素,删除某个元素等。那么在awk中,想要输出所有元素呢?我们可以借助前边提到的for循环语句。我们回顾一下for循环语句
在实际工作中,我们经常需要使用数组来统计一个字符串出现的次数。比如统计日志中每个IP出现的次数,我们可以使用数组来统计。但是,有时候我们需要使用一些特殊用法,后边再细聊。
在awk中,我们可以进行数值运算,示例如下:
[root@linux2 ~]# awk 'BEGIN{a=1; print a; a=a+1; print a}' 1 2 [root@linux2 ~]# [root@linux2 ~]# awk 'BEGIN{a=1; print a; a++; print a}' 1 2 [root@linux2 ~]#
如图:
a的值为1 ,自加后,打印之后a的值增加1。这里a本身就是一个数字,那么如果a是字符串,能否进行自家运算呢,我们试一下:
[root@linux2 ~]# awk 'BEGIN{a="test"; print a; a++; print a; a++; print a}' test 1 2 [root@linux2 ~]# awk 'BEGIN{a=""; print a; a++; print a; a++; print a}' 1 2 [root@linux2 ~]#
如图:
如上所示,在awk中,如果变量为字符串,也可以进行自加运算。如果字符串参与运算,字符串将呗当作数字0进行运算。空字符串一样在参与运算时,也会被当做数字0。那么如果我们引用一个不存在的元素,并对其进行自加运算的结果如何,来试一下:
[root@linux2 ~]# awk 'BEGIN{print arr["ip"]; arr["ip"]++; print arr["ip"]}' 1 [root@linux2 ~]# awk 'BEGIN{print arr["ip"]; arr["ip"]++; arr["ip"]++; print arr["ip"]}' 2
如图:
如上所示,引用一个不存在的元素时。元素被赋值为一个空字符串,当对这个元素进行自加运算,这个元素就被当成0。加一次就变成了1。利用这个特性,我们可以统计文本中某些字符出现的次数。比如IP地址,示例如下:
[root@linux2 ~]# cat test8.txt 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@linux2 ~]# awk '{count[$1]++} END{for(i in count){print i, count[i]}}' test8.txt 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@linux2 ~]# cat test3.txt Allen Phillips Green Lee William Aiden James Lee Angel Jack Tyler Kevin Lucas Thomas Kevin [root@linux2 ~]# awk '{for(i=1;i<=NF;i++){count[$i]++}} END{for(j in count){print j, count[j]}}' test3.txt 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数组的用法,我们先总结这么多。这并不是数组的全部,上边这么多其实已经够用了。
补充:
echo "A B C D" l awk '{OFS="I"; print $0;$1=$1 ;print $0} '
如图:
$1-$1是用来激活$0的重新赋值,也就是说字段$1...和字段数NF的改变会促便awk重新计算$0的值,通常是在改变OFS后而需要输出$0时这样做
awk ‘BEGIN{a[0]=10;a[1]=20; print a[1]}’
awk 'BEGIN{a[o]=10;a[1]=20; print a[o]'
awk 'BEGIN{a[ "abc"]=10; a["xyz"]=20;print a ["abc"]}'
awk 'BEGINia [ "abc"]=10;a ["xyz"]=20; print a["xyz"]}’
awk 'BEGIN{a ["abe"]="aabbcc"; a["xyz"]="xyyzz" ; print a ["xyz"]}'
awk 'BEGIN{a[0]=10;a[1]=20;a[2]=30;for(i in a){print i,a[i]}}'
如图:
PS1:BEGIN中的命令只执行一次
PS2: awk数组的下标除了可以使用数字,也可以使用字符串,字符串需要使用双引号
使用awk统计httpd访问日志中每个客户端IP的出现次数
awk '(ip[$1]++);END{for(i in ip){print ip[i],i}}' /var/log/httpd/access_log
如图:
注:定义数组,数组名称为ip,数字的下标为日怎文件的第1列(也就是客户A的i?地址),+t的目的在于对客户端进行统计计数,客户端IP出现―次计数器就加i。END中的指令在读取完文件后执行,通过循环将所有统计信息输出,for循环遍历的是数组名ip的下标。
脚本编写访问主机超过几次报警
#!/bin/ bash
x='awk '/Failed password/{ip[$11]++};END{for(i in ip) (print i","ip[i]}}’/var/log/secure'
#190.168.80.13 3
for j in $x
do
ip= "echo $j l awk -F ", " '{print $1}"
num="echo $j l awk -F ", " '{print $2} "
if[ $num -ge 3 ];then
echo“警告$ip访问本机失败了$num次,请速速处理!”
fi
done
把当前系统使用频率最高的前10条命令输出
awk '{ comm[$1]++}END{for( i in comm){print i,comm[i]}}' /root/.bash_history | sort -rnk 2 | head
其中各个命令的含义:
comm[$1]++} :$1是文件中的第一列 ++ 当第一列数据相同时自加1
END{for( i in comm)
{print i,comm[i]}}'
/root/.bash_history
| sort -rnk 2
| head
2 、输出当前系统1小时内占用CPU最多的前10个进程
#!/bin/bash
for((i=0;i<60;i++))
do
ps -eo comm,pcpu | tail -n +2 >> /tmp/top10.txt
sleep 60
done
awk '{ process[$1]+=$2}END{for( j in process){ print i,process[i]}}' /tmp/top10.txt | sort -rnk 2 | head
7.awk内置函数
awk中,可以自定义函数,也有内置函数,我们今天来说一下常用的内置函数。awk的内置函数大致可以分为算数函数,字符串函数,时间函数,其他函数等,这里我们介绍一些常用的内置函数。
算数函数
最常用的算数函数有rand函数,srand函数,int函数
可以使用rand函数生成随机数,使用rand函数时,需要配合srand函数。否则rand函数返回的值将一成不变。
[root@linux2 ~]# awk 'BEGIN{print rand()}' 0.237788 [root@linux2 ~]# awk 'BEGIN{print rand()}' 0.237788 [root@linux2 ~]# awk 'BEGIN{print rand()}' 0.237788
如图:
可以看到,如果单纯的使用rand函数,生成的值是不变的。可以配合srand函数,生成一个大于0小于1的随机数。示例如下:
[root@linux2 ~]# awk 'BEGIN{srand(); print rand()}' 0.0990968 [root@linux2 ~]# awk 'BEGIN{srand(); print rand()}' 0.656783 [root@linux2 ~]# awk 'BEGIN{srand(); print rand()}' 0.453447
如图:
可以看到,上例中生成的随机数都是小于1 的小数,如果我们想要生成随机整数。可以将上例中生成的随机数乘以100,然后截取证书部分,使用int函数可以截取整数部分的值,示例如下:
[root@linux2 ~]# awk 'BEGIN{srand();print rand()}' 0.00729847 [root@linux2 ~]# awk 'BEGIN{srand();print 100*rand()}' 48.4798 [root@linux2 ~]# awk 'BEGIN{srand();print int(100*rand())}' 88
如图:
字符串函数
我们可以使用gsub函数或者sub函数替换某些文本。
如果我们想把一个文本中的小写字母l都换成大写字母L。则可以使用gsub函数,示例如下:
[root@linux2 ~]# cat test9.txt Allen Pjillips Green Lee William Ken Allen [root@linux2 ~]# awk '{gsub("l","L",$1); print $0}' test9.txt ALLen Pjillips Green Lee WiLLiam Ken Allen
如图:
如上所示,我们使用gsub函数将小写字母l替换成大写字母L。但是替换范围只局限于$1。所以我们看到只有第一列的小写字母l替换成了大写米姆L,其他列并没有替换。如果像替换文本中所有的小写字母l,可以把$1换成$0。或者直接省略第三个参数,省略第三个参数时默认是$0。
[root@linux2 ~]# awk '{gsub("l","L",$0);print $0}' test9.txt ALLen PjiLLips Green Lee WiLLiam Ken ALLen [root@linux2 ~]# awk '{gsub("l","L");print $0}' test9.txt ALLen PjiLLips Green Lee WiLLiam Ken ALLen
如图:
通过上述例子,应该已经明白,gsub函数会在指定范围内查找指定的字符,并将其替换为指定字符串。
其实我们呢还可以根据正则表达式,替换字符串,示例如下:
[root@linux2 ~]# cat test9.txt Allen Pjillips Green Lee William Ken Allen [root@linux2 ~]# awk '{gsub("[a-z]","6",$1);print $0}' test9.txt A6666 Pjillips G6666 Lee W666666 Ken Allen
如图:
我们再来看一下sub函数,sub函数和gsub函数有什么区别呢。来对比一下:
[root@linux2 ~]# awk '{sub("l","L",$1);print $0}' test9.txt ALlen Pjillips Green Lee WiLliam Ken Allen [root@linux2 ~]# awk '{gsub("l","L",$1);print $0}' test9.txt ALLen Pjillips Green Lee WiLLiam Ken Allen
如图:
从示例可以看出,当使用gsub函数时,gsub会替换指定范围内所有符合条件的字符。当使用sub函数时,sub函数只会替换指定范围内第一次匹配到的符合条件的字符。我们可以理解gsub是指定范围内的全局替换,sub是指定范围内的单次替换。
length函数,获取指定字符串的长度。示例如下:
[root@linux2 ~]# cat test9.txt Allen Pjillips Green Lee William Ken Allen [root@linux2 ~]# awk '{for(i=1;i<=NF;i++){print $i, length($i)}}' test9.txt Allen 5 Pjillips 8 Green 5 Lee 3 William 7 Ken 3 Allen 5
如图:
如上所示,我们输出了每个字符串的长度。其实,length可以省略传入的参数,即不知道任何字符。当省略参数时,默认使用$0作为参数。这样我们就可以使用length函数,获取到文本每一行的长度。示例如下:
[root@linux2 ~]# awk '{print $0, length()}' test9.txt Allen Pjillips 14 Green Lee 9 William Ken Allen 17
如图:
index函数,获取指定字符位于整个字符串中的位置。示例如下:
[root@linux2 ~]# cat test9.txt Allen Pjillips Green Lee William Ken Allen [root@linux2 ~]# awk '{print index($0,"Lee")}' test9.txt 0 7 0
如图:
上例中,我们使用index函数,在每一行寻找字符串"Lee"。如果Lee存在于当前行,则返回字符串在当前行的位置。如果不存于当前行则返回0,第二行包含Lee。Lee位于第二行地七个字符的位置,所以返回7。
split函数,将指定字符串按照指定的分隔符切割。
在前边提到数组时,我们提到可以通过split函数动态生成数组,而不用手动设置数组中每个元素的值。示例如下:
[root@linux2 ~]# awk -v ts="大娃:二娃:三娃" 'BEGIN{split(ts,huluwa,":");for(i in huluwa){print huluwa[i]}}' 大娃 二娃 三娃
如图:
如上所示,我们通过split函数将字符串ts切割。以 :作为分隔符,将分割后的字符串保存到名为huluwa的数组中。当我们输出数组时,每个元素的值为分割后的字符。其实,split函数本身也有返回值,其返回值就是分割以后的数组长度,示例如下:
[root@linux2 ~]# awk -v ts="大娃:二娃:三娃" 'BEGIN{print split(ts,huluwa,":")}' 3
注意:被split分割后的数组的元素时从下标1开始的,而且数组中输出的元素可能与字符串中字符的顺序不同。原因上边讲数组的时候已经聊过了,如果想要按照顺序输出数组中的元素,可以使用以下方法:
[root@linux2 ~]# awk -v ts="qq te ab th" 'BEGIN{split(ts,arr," ");for(i in arr){print arr[i]}}' th qq te ab [root@linux2 ~]# 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@linux2 ~]# 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@linux2 ~]# 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@linux2 ~]# 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@linux2 ~]# 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@linux2 ~]# 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@linux2 ~]# 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@linux2 ~]# 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@linux2 ~]# 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
如图:
8.awk之三元运算和打印奇偶行
三元运算
在前边介绍if...else结构时,我们有个例子,centos7中,ID小于1000的是系统用户,大于1000的是普通用户。我们以centos7为例,用awk找出哪些是系统用户,哪些是普通用户。
[root@linux2 ~]# awk -v FS=":" '{if($3<1000){usertype="系统用户"}else{usertype="普通用户"}; print $1, usertype}' /etc/passwd root 系统用户 bin 系统用户 daemon 系统用户 adm 系统用户 lp 系统用户 sync 系统用户 shutdown 系统用户 halt 系统用户 mail 系统用户 operator 系统用户 games 系统用户 ftp 系统用户 nobody 系统用户 systemd-network 系统用户 dbus 系统用户 polkitd 系统用户 sshd 系统用户 postfix 系统用户 es 普通用户 ntp 系统用户 user1 普通用户 user2 普通用户
如图:
其实,我们可以通过三元运算,来替换if...else结构。示例如下:
[root@linux2 ~]# awk -v FS=":" '{usertype=$3<1000?"系统用户":"普通用户"; print $1, usertype}' /etc/passwd root 系统用户 bin 系统用户 daemon 系统用户 adm 系统用户 lp 系统用户 sync 系统用户 shutdown 系统用户 halt 系统用户 mail 系统用户 operator 系统用户 games 系统用户 ftp 系统用户 nobody 系统用户 systemd-network 系统用户 dbus 系统用户 polkitd 系统用户 sshd 系统用户 postfix 系统用户 es 普通用户 ntp 系统用户 user1 普通用户 user2 普通用户
如图:
如上所示,我们利用三元运算代替了if...else结构。三元运算的语法如下:
条件?结果1 :结果2
如果条件成立,则返回结果1,条件不成立返回结果2。
其实三运运算还有另外一种形式, 表达式1? 表达式2 :表达式3 示例如下:
[root@linux2 ~]# awk -v FS=":" '{usertype=$3<1000? a++:b++} END{print a, b}' /etc/passwd 20 15
如图:
通过上述命令,统计出了,系统用户有20个,普通用户有15个。
打印奇偶行
我们想要使用awk打印文本中的奇数行或偶数行。是很简单的,我们先来看一下:
[root@linux2 ~]# cat test10.txt 第 1 行 第 2 行 第 3 行 第 4 行 第 5 行 第 6 行 第 7 行 [root@linux2 ~]# awk 'i=!i' test10.txt 第 1 行 第 3 行 第 5 行 第 7 行 [root@linux2 ~]# awk '!(i=!i)' test10.txt 第 2 行 第 4 行 第 6 行
如图:
如上所示,我们打印了文本的奇数行和偶数行。想知道原理,需要明白以下两点
(1)、在awk中,如果省略了模式对应的动作。当前行满足模式时,默认动作为打印整行。即{print $0}
(2)、在awk中,0或空字符串表示假,非0或者非空字符串表示真
我们来详细说一下以上两点。
之前介绍过,模式可以理解为条件。如果当前行与模式匹配,则执行相应的动作。示例如下:
[root@linux2 ~]# awk '/1/{print $0}' test10.txt 第 1 行 [root@linux2 ~]# awk '$2>5{print $0}' test10.txt 第 6 行 第 7 行 [root@linux2 ~]# awk '/1/' test10.txt 第 1 行 [root@linux2 ~]# awk '$2>5' test10.txt 第 6 行 第 7 行
如图:
由上四个例子,我们发现。如果awk命令中的动作省略,会默认输出整行
注意:空模式和BEGIN/END模式除外。
第2点,在awk中,0或空字符串表示假,非0或这非空字符串表示真。怎么理解呢,模式可以理解为条件,条件成立则为真,条件不成立则为假。所以模式为真执行相应的动作,模式为假时不执行相应的动作。那么能不能直接把模式替换为真或者假呢,我们试一下:
[root@linux2 ~]# cat test11.txt abcd [root@linux2 ~]# awk '{print $0}' test11.txt abcd [root@linux2 ~]# awk '1{print $0}' test11.txt abcd [root@linux2 ~]# awk '2{print $0}' test11.txt abcd [root@linux2 ~]# awk '2' test11.txt abcd [root@linux2 ~]# awk '0{print $0}' test11.txt [root@linux2 ~]# awk '0' test11.txt [root@linux2 ~]#
如图:
由上面几个例子,我们可以得出,只要模式为真,就执行动作,模式为假,不执行动作。其实还可以对模式取非,非真即假,非假即真。
[root@linux2 ~]# awk '0' test11.txt [root@linux2 ~]# awk '!0' test11.txt abcd [root@linux2 ~]# awk '5' test11.txt abcd [root@linux2 ~]# awk '!5' test11.txt [root@linux2 ~]#
如图:
我们再来延伸以下
[root@linux2 ~]# awk 'i=1' test11.txt abcd
如图:
上例中,其实使用了awk的变量,将变量i赋值为1。当i=1以后,i为非零值,表示为真,所以上述例子输出了所有行。这时候,我们再来看打印奇数行的示例:
[root@linux2 ~]# awk 'i=!i' test10.txt 第 1 行 第 3 行 第 5 行 第 7 行
如图:
当awk处理第一行时,变量i被初始化,值为空。对空取非,所以此时可以认为模式为真。所以输出了第一行,同时取非后的值由赋予了变量i。此时i为真,当处理第二行时,对i再次取肥 ,i又变成了假,所以第二行没有输出。以此类推就实现了打印奇偶行。
9、awk高级用法
调用函数getline:读取一行数据的时候并不是得到当前行而是当前行的下一行
awk通过管道符号、双引号调用shell命令
echo $PATH | awk 'BEGIN{RS=":"};END {print NR}’ #统计以冒号分隔的文本段落数,END语句块中,往往会放入打印结果等语句
如图:
awk -F: '/bash$/{print | "wc -l")' /etc/passwd #调用wc -l命令统计使用bash 的用户个数,等同于grep -c "bash$" /etc/passwd awk -F: ‘/ bash$/ {print}' pasawd l wc -l
如图:
查看内容百分比
free -m l awk '/Mem:/ {print int($3/($3+$4)*100)"%")′
free -m|awk ‘/Mem:/ {print $3/$2*100}' free -m | awk '/Mem:/{print $3/$2*100}' | awk -F. '{print $1"%"}' 比较后取整
如图:
查看CPU利用率
top -b -n 1 l grep Cpu | awk -F, {print $4}' l awk '{print $1}′ #查看当前cPv空闲率,(-b -n1表示只需要1次的输出结果) CPu使用率 cpu_us=`top -b -n 1 l grep Cpu | awk '{ print $2}` cpu_sy=`top -b -n 1 | grep Cpu | awk -F, '{print $2}' | awk '{print $1}'` cpu_sum=$ (($cpu_us+$cpu_sy)) echo $cpu_sum
如图:
查看重启时间
last 是你关机或重启的记录,last | tail -1 是最后一次关机或重启的时间; 开机时间记录在/proc/uptime中可用 date -d "$(awk -F. '{print $1}' /proc/uptime) second ago" +"%Y-%m-%d %H:%M:%S" 来查看开机时间,和运行时间。(+号前面有空格)
显示上一次重启时间
调用w命令,并用来统计在线用户数
五、awk统计案例
1、统计系统中各种类型的shell
# awk -F: '{ shells[$NF]++ };END{for (i in shells) {print i,shells[i]} }' /etc/passwd books[linux]++ books[linux]=1 shells[/bin/bash]++ shells[/sbin/nologin]++ /bin/bash 5 /sbin/nologin 6 shells[/bin/bash]++ a shells[/sbin/nologin]++ b shells[/sbin/shutdown]++ c books[linux]++ books[php]++
2、统计网站访问状态
# ss -antp|grep 80|awk '{states[$1]++};END{for(i in states){print i,states[i]}}' TIME_WAIT 578 ESTABLISHED 1 LISTEN 1 # ss -an |grep :80 |awk '{states[$2]++};END{for(i in states){print i,states[i]}}' LISTEN 1 ESTAB 5 TIME-WAIT 25 # ss -an |grep :80 |awk '{states[$2]++};END{for(i in states){print i,states[i]}}' |sort -k2 -rn TIME-WAIT 18 ESTAB 8 LISTEN 1
3、统计访问网站的每个IP的数量
# netstat -ant |grep :80 |awk -F: '{ip_count[$8]++};END{for(i in ip_count){print i,ip_count[i]} }' |sort # ss -an |grep :80 |awk -F":" '!/LISTEN/{ip_count[$(NF-1)]++};END{for(i in ip_count){print i,ip_count[i]}}' |sort -k2 -rn |head
4、统计网站日志中PV量
统计Apache/Nginx日志中某一天的PV量 <统计日志> # grep '27/Jul/2017' mysqladmin.cc-access_log |wc -l 14519 统计Apache/Nginx日志中某一天不同IP的访问量 <统计日志> # grep '27/Jul/2017' mysqladmin.cc-access_log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' |sort -k2 -rn |head # grep '07/Aug/2017' access.log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' |awk '$2>100' |sort -k2 -rn
名词解释:
网站浏览量(PV) 名词:PV=PageView (网站浏览量) 说明:指页面的浏览次数,用以衡量网站用户访问的网页数量。多次打开同一页面则浏览量累计。用户每打开一个页面便记录1次PV。 名词:VV = Visit View(访问次数) 说明:从访客来到您网站到最终关闭网站的所有页面离开,计为1次访问。若访客连续30分钟没有新开和刷新页面,或者访客关闭了浏览器,则被计算为本次访问结束。 独立访客(UV) 名词:UV= Unique Visitor(独立访客数) 说明:1天内相同的访客多次访问您的网站只计算1个UV。 独立IP(IP) 名词:IP=独立IP数 说明:指1天内使用不同IP地址的用户访问网站的数量。同一IP无论访问了几个页面,独立IP数均为1
六、关于AWK的10个经典案例
1、分析访问日志(Nginx为例)
日志格式: '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"' 统计访问IP次数: # awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log 统计访问访问大于100次的IP: # awk '{a[$1]++}END{for(v ina){if(a[v]>100)print v,a[v]}}' access.log 统计访问IP次数并排序取前10: # awk '{a[$1]++}END{for(v in a)print v,a[v]|"sort -k2 -nr |head -10"}' access.log 统计时间段访问最多的IP: # awk'$4>="[02/Jan/2017:00:02:00" &&$4< ="[02/Jan/2017:00:03:00"{a[$1]++}END{for(v in a)print v,a[v]}'access.log 统计上一分钟访问量: # date=$(date -d '-1 minute'+%d/%d/%Y:%H:%M) # awk -vdate=$date '$4~date{c++}END{printc}' access.log 统计访问最多的10个页面: # awk '{a[$7]++}END{for(vin a)print v,a[v]|"sort -k1 -nr|head -n10"}' access.log 统计每个URL数量和返回内容总大小: # awk '{a[$7]++;size[$7]+=$10}END{for(v ina)print a[v],v,size[v]}' access.log 统计每个IP访问状态码数量: # awk '{a[$1" "$9]++}END{for(v ina)print v,a[v]}' access.log 统计访问IP是404状态次数: # awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print v,a[v]}' access.log
2、两个文件差异对比
文件内容: # seq 1 5 > a # seq 3 7 > b 找出b文件在a文件相同记录: 方法1: # awk 'FNR==NR{a[$0];next}{if($0 in a)print $0}' a b 3 4 5 # awk 'FNR==NR{a[$0];next}{if($0 in a)print FILENAME,$0}' a b b 3 b 4 b 5 # awk 'FNR==NR{a[$0]}NR>FNR{if($0 ina)print $0}' a b 3 4 5 # awk 'FNR==NR{a[$0]=1;next}(a[$0]==1)' a b # a[$0]是通过b文件每行获取值,如果是1说明有 # awk 'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}' a b 3 4 5 方法2: # awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' a b 3 4 5 方法3: # awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]==1' a b 3 4 5 找出b文件在a文件不同记录: 方法1: # awk 'FNR==NR{a[$0];next}!($0 in a)' a b 6 7 # awk 'FNR==NR{a[$0]=1;next}(a[$0]!=1)' a b # awk'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}' a b 6 7 方法2: # awk'FILENAME=="a"{a[$0]=1}FILENAME=="b" && a[$0]!=1' a b 方法3: # awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]!=1' a b
3、合并两个文件
文件内容: # cat a zhangsan 20 lisi 23 wangwu 29 # cat b zhangsan man lisi woman wangwu man 将a文件合并到b文件: 方法1: # awk 'FNR==NR{a[$1]=$0;next}{print a[$1],$2}' a b zhangsan 20 man lisi 23 woman wangwu 29 man 方法2: # awk 'FNR==NR{a[$1]=$0}NR>FNR{print a[$1],$2}' a b zhangsan 20 man lisi 23 woman wangwu 29 man 将a文件相同IP的服务名合并: # cat a 192.168.1.1: httpd 192.168.1.1: tomcat 192.168.1.2: httpd 192.168.1.2: postfix 192.168.1.3: mysqld 192.168.1.4: httpd # awk 'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1] $2}END{for(v in a)print v,a[v]}' a 192.168.1.4: httpd 192.168.1.1: httpd tomcat 192.168.1.2: httpd postfix 192.168.1.3: mysqld 解读: 数组a存储是$1=a[$1] $2,第一个a[$1]是以第一个字段为下标,值是a[$1] $2,也就是$1=a[$1] $2,值的a[$1]是用第一个字段为下标获取对应的值,但第一次数组a还没有元素,那么a[$1]是空值,此时数组存储是192.168.1.1=httpd,再遇到192.168.1.1时,a[$1]通过第一字段下标获得上次数组的httpd,把当前处理的行第二个字段放到上一次同下标的值后面,作为下标192.168.1.1的新值。此时数组存储是192.168.1.1=httpd tomcat。每次遇到相同的下标(第一个字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。
4、将第一列合并到一行
# cat file 1 2 3 4 5 6 7 8 9 # awk '{for(i=1;i< =NF;i++)a[i]=a[i]$i" "}END{for(vin a)print a[v]}' file 1 4 7 2 5 8 3 6 9 解读: for循环是遍历每行的字段,NF等于3,循环3次。 读取第一行时: 第一个字段:a[1]=a[1]1" " 值a[1]还未定义数组,下标也获取不到对应的值,所以为空,因此a[1]=1 。 第二个字段:a[2]=a[2]2" " 值a[2]数组a已经定义,但没有2这个下标,也获取不到对应的值,为空,因此a[2]=2 。 第三个字段:a[3]=a[3]3" " 值a[2]与上面一样,为空,a[3]=3 。 读取第二行时: 第一个字段:a[1]=a[1]4" " 值a[2]获取数组a的2为下标对应的值,上面已经有这个下标了,对应的值是1,因此a[1]=1 4 第二个字段:a[2]=a[2]5" " 同上,a[2]=2 5 第三个字段:a[3]=a[3]6" " 同上,a[2]=3 6 读取第三行时处理方式同上,数组最后还是三个下标,分别是1=1 4 7,2=2 5 8,3=36 9。最后for循环输出所有下标值。
5、字符串拆分
字符串拆分: 方法1: # echo "hello" |awk -F '''{for(i=1;i< =NF;i++)print $i}' h e l l o 方法2: # echo "hello" |awk '{split($0,a,"''");for(v in a)print a[v]}' l o h e l
6、统计出现的次数
统计字符串中每个字母出现的次数: # echo "a.b.c,c.d.e" |awk -F'[.,]' '{for(i=1;i< =NF;i++)a[$i]++}END{for(v in a)print v,a[v]}' a 1 b 1 c 2 d 1 e 1
7、费用统计
得出每个员工出差总费用及次数: # cat a zhangsan 8000 1 zhangsan 5000 1 lisi 1000 1 lisi 2000 1 wangwu 1500 1 zhaoliu 6000 1 zhaoliu 2000 1 zhaoliu 3000 1 # awk '{name[$1]++;cost[$1]+=$2;number[$1]+=$3}END{for(v in name)print v,cost[v],number[v]}' a zhangsan 5000 1 lisi 3000 2 wangwu 1500 1 zhaoliu 11000 3
8、获取某列数字最大数
# cat a a b 1 c d 2 e f 3 g h 3 i j 2 获取第三字段最大值: # awk 'BEGIN{max=0}{if($3>max)max=$3}END{print max}' a 3 打印第三字段最大行: # awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)if(a[v]==max)print v}'a g h 3 e f 3
9、去除文本第一行和最后一行
# seq 5 |awk'NR>2{print s}{s=$0}' 2 3 4 解读: 读取第一行,NR=1,不执行print s,s=1 读取第二行,NR=2,不执行print s,s=2 (大于为真) 读取第三行,NR=3,执行print s,此时s是上一次p赋值内容2,s=3 最后一行,执行print s,打印倒数第二行,s=最后一行
10、获取Nginx upstream块内后端IP和端口
# cat a upstream example-servers1 { server 127.0.0.1:80 weight=1 max_fails=2fail_timeout=30s; } upstream example-servers2 { server 127.0.0.1:80 weight=1 max_fails=2fail_timeout=30s; server 127.0.0.1:82 backup; } # awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' a 127.0.0.1:80 # awk '/example-servers1/,/}/{if(i>1)print s;s=$2;i++}' a # awk '/example-servers1/,/}/{if(i>1){print s}{s=$2;i++}}' a 127.0.0.1:80 解读: 读取第一行,i初始值为0,0>1为假,不执行print s,x=example-servers1,i=1 读取第二行,i=1,1>1为假,不执行prints,s=127.0.0.1:80,i=2 读取第三行,i=2,2>1为真,执行prints,此时s是上一次s赋值内容127.0.0.1:80,i=3 最后一行,执行print s,打印倒数第二行,s=最后一行。 这种方式与上面一样,只是用i++作为计数器。
awk练习题:
1、只显示uid是501的用户的详细信息 awk -F ":" '$3=="501"{print $0}' /etc/passwd 2、输出文件中偶数行的内容 awk 'FNR%2!=1{print $0}' a.txt 3、统计系统内有多少个用户不能登录系统 awk -F ":" '$7~"no"{print $0;i++}END{print i}' /etc/passwd 4、1-100间 是7的倍数或是含7的数显示出来 awk 'FNR%7==0{print $0}{print FNR}' /etc/passwd 5、统计当前系统有多少个内建帐户 6、统计当前系统有多少个自定义帐户 awk -F ":" 'BEGIN{i=0;j=0}$3<500{i++}$3>=500{j++}END{print "uid 小于 500的用户有 "i"个";print "uid 大于等于 500的用户有"j"个"}' /etc/passwd 7、把用户名含数字的用户信息输出 awk -F ":" '$1~/[0-9]/{print $0}' aa.txt 8、显示前5个系统用户的用户名、uid、shell
参考:
https://blog.csdn.net/weixin_56270746/article/details/124650232
https://www.cnblogs.com/jkin/p/10751394.html
https://www.linuxprobe.com/awk-classic-case.html