# Shell 文本处理工具 awk

# **1.**Awk 文本处理

# 1.1 什么是 awk
  • awk 不仅仅是一个文本处理工具,通常用于处理数据并生成结果报告。
  • 当然 awk 也是一门编程语言,是 linux 上功能最强大的数据处理工具之一。
# 1.2 为何需要正则表达式
  • 第一种形式:awk 'BEGIN {} pattern {commands} END {}' file_name
  • 第二种形式:standard output | awk BEGIN {} pattern {commands} END {}
  • 第三种形式:awk -f awk-script-file filenames
语法格式含义
BEGIN {}正式处理数据之前执行
pattern匹配模式,正则表达式;grep
处理命令,可能多行
END{}处理完所有匹配数据后执行

1.png

含义解释:

  • BEGIN 发生在读文件之前,所有会在处理之前就执行了 1/2。
  • {} 表示处理文件的过程,由于文件内有三行,所以会执行三次 print。
  • END {} 表示文件处理完毕后的动作。
[root@web01 ~]# awk 'BEGIN{print 1/2} {print "oldxu"} END {print "Over"}' /etc/hosts
0.5
oldxu
oldxu
oldxu
Over
# 1.3 Awk 工作原理

awk -F : '{print $1,$3}' /etc/passwd

  • 1.awk 将文件中的每一行作为输入,并将每一行赋给内部变量 $0, 以换行符结束
  • 2.awk 开始进行字段分解,每个字段存储在已编号的变量中,从 $1 开始 [默认空格分割]
  • 3.awk 默认字段分隔符是由内部 FS 变量来确定,可以使用 - F 修订
  • 4.awk 行处理时使用了 print 函数打印分割后的字段
  • 5.awk 在打印后的字段加上空格,因为 $1,$3 之间有一个逗号。逗号被映射至 OFS 内部变量中,称为输出字段分隔符, OFS 默认为空格
  • 6.awk 输出之后,将从文件中获取另一行,并将其存储在 $0 中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕

AWK 变量总结:

  • 1. 读入一行文件,默认是以 换行作为读入分隔符的(RS),读入进来后,会赋值给 $0,同时会为其编号赋值给 NR 变量;
  • 2. 检查 FS 变量是否有指定字段分隔符,按照字段分隔符拆分成列的形式,将每一列的内容赋值给对应的 $1 $2 $3 等内部变量。同时会将分隔后的总列数赋值给 NF 变量;
  • 3. 输出内容,输出时候会使用 print,$1,$3 这个逗号是输出字段分隔符,由 OFS 变量控制,默认是空;
  • 4. 输出内容默认是按照换行符展示,由 ORS 控制,控制输出行分隔符,默认是换行符;
# 1.4 Awk 内部变量
内置变量含义
$0整行内容
$1-$n当前行的第 1-n 个字段
NF当前行的字段个数,也就是多少列
NR当前的行号,从 1 开始计数
FS输入字段分隔符。不指定默认以空格或 tab 键分割
RS输入行分隔符。默认回车换行
OFS输出字段分隔符。默认为空格
ORS输出行分隔符。默认为回车换行

要想了解 awk 的一些内部变量需要先准备如下数据文件:

[root@web01 ~]# cat file.txt 
ll 1990 50 51 61
kk 1991 60 52 62
hh 1992 70 53 63
jj 1993 80 54 64
mm 1994 90 65
# 1.4.1 FS 指定分隔符

awk 通过内置变量,FS 来指定字段分割符, 默认以空白行作为分隔符

1. 输出文件中的第一列

[root@web01 ~]# awk '{print $1}' file.txt 
ll
kk
hh
jj
mm

2. 修改文件,然后指定多个分隔符,获取第一列内容

[root@web01 ~]# cat file.txt 
ll:1990 50 51 61
kk:1991 60 52 62
hh 1992 70 53 63
jj 1993 80 54 64
mm 1994 90 55 65
#以冒号或空格为分隔符
[root@web01 ~]# awk -F '[: ]' '{print $2}' file.txt 
1990
1991
1992
1993
1994

3. 再次修改文件

[root@web01 ~]# cat awk_file.txt
ll::1990 50 51 61
kk:1991 60 52 62
hh 1992 70 53 63
jj 1993 80 54 64
mm 1994 90 55 65
#[:]+ 连续的多个冒号当一个分隔符,连续的多个空格当一个分隔符,连续空格和冒号也当做一个字符来处理
[root@web01 ~]# awk -F '[: ]+' '{print $2}' file.txt 
1990
1991
1992
1993
1994
# 1.4.2 NF 获取最后一列

awk 通过内置变量,NF 保存每行的最后一列内容

1. 通过 print 打印,NF 和 $NF,你发现了什么?

[root@web01 ~]# awk '{print $NF,NF}' file.txt 
61 5
62 5
63 5
64 5
65 5

2. 如果将文件第五行的 65 置为空,那么该如何在获取最后一列的数字?

[root@web01 ~]# awk '{print $NF}' file.txt 
61
62
63
64
55

3. 如果一个文件很长,靠数列数需要很长的时间,那如何快速打印倒数第二列

[root@web01 ~]# awk '{print $(NF-1)}' file.txt 
51
52
53
54
90
# 1.4.3 NR 获取每行行号

awk 通过内置变量,NR 获取每行行号

1. 使用 print 打印 NR 变量,会发现 NR 会记录每行文件的行号

[root@web01 ~]# awk '{print NR,$0}' file.txt 
1 ll 1990 50 51 61
2 kk 1991 60 52 62
3 hh 1992 70 53 63
4 jj 1993 80 54 64
5 mm 1994 90 55

2. 如果想打印第二行到第三行的内容,怎么做?

[root@web01 ~]# awk 'NR>1&&NR<4 {print NR,$0}' file.txt 
2 kk 1991 60 52 62
3 hh 1992 70 53 63
# 如果想打印第三行,怎么做
[root@web01 ~]# awk 'NR==3 {print NR,$0}' file.txt
3 hh 1992 70 53 63

3. 如果想打印第三行,同时还要打印第一列

[root@web01 ~]# awk 'NR==3 {print NR,$1}' file.txt 
3 hh
# 1.4.4 RS 读入行分隔符

awk 通过内置变量 RS,对读入的文本进行分隔符指定;

1. 准备文件内容

[root@web01 ~]# cat file2.txt 
Linux|Shell|Nginx--docker|Gitlab|jenkins--mysql|redis|mongodb

2. 读入文件,并以 -- 作为读入分隔符,然后将文件拆分为三列;

[root@web01 ~]# awk 'BEGIN{RS="--"}{print $0}' file2.txt 
Linux|Shell|Nginx
docker|Gitlab|jenkins
mysql|redis|mongodb
# 1.4.5 OFS 输出字段分隔符

awk 内置变量 OFS,输出字段分隔符,初始情况下 OFS 变量是空格。

[root@web01 ~]# awk 'BEGIN{RS="--";FS="|";OFS=":"} {print $1,$3}' file2.txt 
Linux:Nginx
docker:jenkins
mysql:mongodb
# 1.4.6 ORS 输出行分隔符

awk 内置变量 ORS,输出行分隔符,默认行分割符为 \n

[root@web01 ~]# awk 'BEGIN{RS="--";FS="|";OFS=":";ORS="==="} {print $1,$3}' file2.txt 
Linux:Nginx===docker:jenkins===mysql:mongodb
# 1.5 Awk 格式输出 Printf

awk 可以通过 printf 函数生成非常漂亮的数据报表。

# 1.5.1 printf 语法
格式符含义
%s打印字符串
%d打印十进制数(整数)
%f打印一个浮点数(小数)
%x打印十六进制数
修饰符含义
-左对齐
+右对齐
# 1.5.2 printf 示例

1.printf 默认没有分隔符。

[root@web01 ~]# awk 'BEGIN{FS=":"}{printf $1}' /etc/passwd
rootbindaemonadmlpsyncshutdownhaltmailoperatorgamesftpnobodysystemd-networkdbuspolkitdtssabrtsshdpostfixrpcrpcusernfsnobodyntpnginxwwwapachexuyongtestzhangwujizhaominxiexunyangxiao

2. 加入换行,格式化输出。

[root@web01 ~]# awk 'BEGIN{FS=":"}{printf "%s\n",$1}' /etc/passwd
root
bin
daemon
adm
lp
sync
shutdown

3. 使用占位符美化输出。

[root@web01 ~]# awk 'BEGIN{FS=":"} {printf "%20s %20s\n",$1,$7}' /etc/passwd
                root            /bin/bash
                 bin        /sbin/nologin
              daemon        /sbin/nologin
                 adm        /sbin/nologin
                  lp        /sbin/nologin

4. 默认右对齐,- 表示左对齐。

[root@web01 ~]# awk 'BEGIN{FS=":"} {printf "%-20s %-20s\n",$1,$7}' /etc/passwd
root                 /bin/bash           
bin                  /sbin/nologin       
daemon               /sbin/nologin       
adm                  /sbin/nologin       
lp                   /sbin/nologin
# 1.5.3 printf 实践

需求:美化一个成绩表

[root@web01 ~]# cat >>student.txt<<EOF
oldxu 80 90 96 98
xiaowang 93 98 92 91
xiaohong 78 76 87 92
xiaoming 86 89 68 92
xiaoxiao 85 95 75 90
EOF

编写 awk 处理脚本

[root@web01 ~]# cat student.awk 
BEGIN {
	printf "%-10s%-10s%-10s%-10s%-10s\n",
	"Name","Yuwen","Shuxue","yinyu","qita"
}
{
	printf "%-10s%-10d%-10d%-10d%-10d\n",
	$1,$2,$3,$4,$5
}

最终处理的结果

[root@web01 ~]# awk -f student.awk student.txt 
Name      Yuwen     Shuxue    yinyu     qita      
oldxu     80        90        96        98        
xiaowang  93        98        92        91        
xiaohong  78        76        87        92        
xiaoming  86        89        68        92        
xiaoxiao  85        95        75        90

美化添加合计

[root@web01 ~]# cat student.awk 
BEGIN {
	printf "%-10s%-10s%-10s%-10s%-10s%-10s\n",
	"Name","Yuwen","Shuxue","yinyu","qita","total"
}
{
	total=$2+$3+$4+$5
	printf "%-10s%-10d%-10d%-10d%-10d%-10d\n",
	$1,$2,$3,$4,$5,total
}
[root@web01 ~]# awk -f student.awk student.txt 
Name      Yuwen     Shuxue    yinyu     qita      total     
oldxu     80        90        96        98        364       
xiaowang  93        98        92        91        374       
xiaohong  78        76        87        92        333       
xiaoming  86        89        68        92        335       
xiaoxiao  85        95        75        90        345
# 1.6 Awk 模式匹配
  • awk 第一种模式匹配:RegExp
  • awk 第二种模式匹配:运算匹配、布尔值匹配、数学运算符匹配
# 1.6.1 RegExp 示例

1. 匹配 /etc/passwd 文件行中含有 root 字符串的所有行。

[root@web01 ~]# awk -F ':' '/root/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

2. 匹配 /etc/passwd 文件行中以 root 开头的行。

[root@web01 ~]# awk '/^root/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash

3. 匹配 /etc/passwd 文件行中 /bin/bash 结尾的行。

[root@web01 ~]# awk '/\/bin\/bash$/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
www:x:666:666::/home/www:/bin/bash
xuyong:x:1000:1000::/home/xuyong:/bin/bash
test:x:1001:1001::/home/test:/bin/bash
zhangwuji:x:1002:1002::/home/zhangwuji:/bin/bash
zhaomin:x:1003:1003::/home/zhaomin:/bin/bash
xiexun:x:1004:1004::/home/xiexun:/bin/bash
yangxiao:x:1005:1005::/home/yangxiao:/bin/bash

4. 提取网卡 IP 地址

[root@web01 ~]# ifconfig ens192 |awk '/inet /{print $2}'
192.168.1.7
# 1.6.2 匹配运算符示例

匹配运算符:

  • <:大于
  • >:大于
  • <=:小于等于
  • >=:大于等于
  • ==:等于
  • !=:不等于
  • ~:正则匹配
  • !~:不匹配正则

1. 以:为分隔符,匹配 /etc/passwd 文件中第 3 个字段小于 50 的行

[root@web01 ~]# awk -F ":" '$3<2{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin

2. 以:为分隔符,匹配 /etc/passwd 文件中第 3 个字段大于 50 的行

[root@web01 ~]# awk -F ":" '$3>1000 {print $0}' /etc/passwd
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
test:x:1001:1001::/home/test:/bin/bash
zhangwuji:x:1002:1002::/home/zhangwuji:/bin/bash
zhaomin:x:1003:1003::/home/zhaomin:/bin/bash
xiexun:x:1004:1004::/home/xiexun:/bin/bash
yangxiao:x:1005:1005::/home/yangxiao:/bin/bash

3. 以:为分隔符,匹配 /etc/passwd 文件中第 7 个字段为 /bin/bash 的行

[root@web01 ~]# awk -F ":" '$7=="/bin/bash" {print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
www:x:666:666::/home/www:/bin/bash
xuyong:x:1000:1000::/home/xuyong:/bin/bash
test:x:1001:1001::/home/test:/bin/bash
zhangwuji:x:1002:1002::/home/zhangwuji:/bin/bash
zhaomin:x:1003:1003::/home/zhaomin:/bin/bash
xiexun:x:1004:1004::/home/xiexun:/bin/bash
yangxiao:x:1005:1005::/home/yangxiao:/bin/bash

4. 以:为分隔符,匹配 /etc/passwd 文件中第 7 个字段不为 /bin/bash 的行

[root@web01 ~]# awk -F ":" '$7!="/bin/bash" {print $0}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

5. 以:为分隔符,匹配 /etc/passwd 文件中第 3 个字段包含 3 个数字以上的行

[root@web01 ~]# awk -F ":" '$3 ~ /[0-9]{3,}/{print $0}' /etc/passwd
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
abrt:x:173:173::/etc/abrt:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
nginx:x:998:996:nginx user:/var/cache/nginx:/sbin/nologin
www:x:666:666::/home/www:/bin/bash
xuyong:x:1000:1000::/home/xuyong:/bin/bash
test:x:1001:1001::/home/test:/bin/bash
zhangwuji:x:1002:1002::/home/zhangwuji:/bin/bash
zhaomin:x:1003:1003::/home/zhaomin:/bin/bash
xiexun:x:1004:1004::/home/xiexun:/bin/bash
yangxiao:x:1005:1005::/home/yangxiao:/bin/bash
# 1.6.3 布尔运算匹配符示例

布尔运算:

  • &&:与
  • |:或
  • !:非

1. 以:为分隔符,匹配 passwd 文件中包含 ftp 或 mail 的行。

[root@web01 ~]# awk 'BEGIN{FS=":"}$1=="ftp" || $1=="mail" {print $0}' /etc/passwd
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

2. 以:为分隔符,匹配 passwd 文件中第 3 个字段小于 50 并且第 4 个字段大于 50 的所有行信息。

[root@web01 ~]# awk 'BEGIN{FS=":"}$3<50 && $4>50{print $0}' /etc/passwd
games:x:12:100:games:/usr/games:/sbin/nologin

3. 匹配没有 /sbin/nologin 的行。

[root@web01 ~]# awk 'BEGIN{FS=":"} $0 !~ /\/sbin\/nologin/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
www:x:666:666::/home/www:/bin/bash
xuyong:x:1000:1000::/home/xuyong:/bin/bash
test:x:1001:1001::/home/test:/bin/bash
zhangwuji:x:1002:1002::/home/zhangwuji:/bin/bash
zhaomin:x:1003:1003::/home/zhaomin:/bin/bash
xiexun:x:1004:1004::/home/xiexun:/bin/bash
yangxiao:x:1005:1005::/home/yangxiao:/bin/bash
# 1.6.4 数学运算符匹配示例

加减乘除运算符:

  • +:加
  • -:减
  • *:乘
  • /:除
  • %:模

1. 计算学生课程分数平均值,学生课程文件内容如下:

[root@web01 ~]# cat student.txt 
oldxu 80 90 96 98
xiaowang 93 98 92 91
xiaohong 78 76 87 92
xiaoming 86 89 68 92
xiaoxiao 85 95 75 90

2. 编写 awk 脚本,实现学员成绩平均值

[root@web01 ~]# cat student.awk 
BEGIN {
	printf "%-10s%-10s%-10s%-10s%-10s%-10s\n",
	"Name","Yuwen","Shuxue","yinyu","qita","Avg"
}
{
	total=$2+$3+$4+$5
	avg=total/(NF-1)
	printf "%-10s%-10d%-10d%-10d%-10d%-10d\n",
	$1,$2,$3,$4,$5,avg
}

3. 执行脚本并观察结果

[root@web01 ~]# awk -f student.awk student.txt 
Name      Yuwen     Shuxue    yinyu     qita      Avg       
oldxu     80        90        96        98        91        
xiaowang  93        98        92        91        93        
xiaohong  78        76        87        92        83        
xiaoming  86        89        68        92        83        
xiaoxiao  85        95        75        90        86
# 1.7 Awk 条件判断
# 1.7.1 单分支判断

if 语句格式:

1. 以:为分隔符,打印当前管理员用户名称

[root@web01 ~]# awk -F ':' '{ if($3==0){print $1 " is adminisitrator"} }' /etc/passwd
root is adminisitrator

2. 以:为分隔符,统计系统用户数量

[root@web01 ~]# awk -F ':' '{if ($3>0 && $3<1000){i++}} END {print "一共有",i,"系统用户"}' /etc/passwd
一共有 25 系统用户

3. 以:为分隔符,统计普通用户数量

[root@web01 ~]# awk -F: '{if ($3>1000) {i++}} END {print "一共有",i,"普通用户"}' /etc/passwd
一共有 6 普通用户

4. 以:为分隔符,只打印 /etc/passwd 中第 3 个字段的数值在 50-100 范围内的行

[root@web01 ~]# awk 'BEGIN{FS=":"}{if($3>50 && $3<100) print $0}' /etc/passwd
nobody:x:99:99:Nobody:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
# 1.7.2 双分支判断

if...else 语句格式: {if (表达式){语句;语句;... }else {语句;语句;...}}

1. 以:为分隔符,判断第三列如果等于 0,则打印该用户名称,如果不等于 0 则打印第七列。

[root@web01 ~]# awk 'BEGIN{FS=":"} { if($3==0) { print $1 } else { print $7 } }' /etc/passwd

2. 以:为分隔符,判断第三列如果等于 0,那么则打印管理员出现的个数,否则都视为系统用户,并打印它的个数

[root@web01 ~]# awk 'BEGIN {FS=":";OFS="\n"} { if ($3==0) { i++ } else { j++ } } END { print i"个管理员" , j"个系统用户" }' /etc/passwd
1 个管理员
32 个系统用户
# 1.7.3 多分支判断

if...else if...else 语句格式: {if (表达式 1) {语句;语句;...} else if (表达式 2) {语句;语句;... } else{语句;语句;... }}

1. 使用 awk if 打印出当前 /etc/passwd 文件管理员有多少个,系统用户有多少个,普通用户有多少个

[root@web01 ~]# cat count_passwd.awk 
BEGIN {
	FS=":"
	OFS="\n"
} 
{ 
	if ($3==0) { 
	   i++ 
	} 
	else if ($3>=201 && $3<1000) { 
	  j++ 
	} 
	else { 
	  k++
	}
} 
END { 
	print i"个管理员", j"个系统用户", k"个普通用户"
}
[root@web01 ~]# awk -f count_passwd.awk /etc/passwd
1个管理员
3个系统用户
29个普通用户

2. 打印 /etc/passwd 文件中 UID 小于 50 的、或 UID 小于 50 大于 100、或 UID 大于 100 的用户名以及 UID

[root@web01 ~]# cat count_password_2.awk
BEGIN {
	FS=":"
}
{
	if ($3<50) {
		printf "%-20s%-10s%-10d\n" , "UID<50",$1,$3
	}
	else if ($3>50 && $3<100) {
		printf "%-20s%-10s%-10s\n" , "UID>50 && UID<100",$1,$3
	}
	else {
		printf "%-20s%-10s%-10s\n" , "UID>100",$1,$3
	}
}
[root@web01 ~]# awk -f count_password_2.awk /etc/passwd
UID<50              root      0         
UID<50              bin       1         
UID<50              daemon    2         
UID<50              adm       3         
UID<50              lp        4         
UID<50              sync      5         
UID<50              shutdown  6         
UID<50              halt      7         
UID<50              mail      8         
UID<50              operator  11        
UID<50              games     12        
UID<50              ftp       14        
UID>50 && UID<100   nobody    99        
UID>100             systemd-network192       
UID>50 && UID<100   dbus      81        
UID>100             polkitd   999       
UID>50 && UID<100   tss       59        
UID>100             abrt      173       
UID>50 && UID<100   sshd      74        
UID>50 && UID<100   postfix   89        
UID<50              rpc       32        
UID<50              rpcuser   29        
UID>100             nfsnobody 65534     
UID<50              ntp       38        
UID>100             nginx     998       
UID>100             www       666       
UID<50              apache    48        
UID>100             xuyong    1000      
UID>100             test      1001      
UID>100             zhangwuji 1002      
UID>100             zhaomin   1003      
UID>100             xiexun    1004      
UID>100             yangxiao  1005
# 1.8 Awk 循环语句
# 1.8.1 while 循环

while 循环:while (条件表达式) 动作

[root@web01 ~]# awk 'BEGIN{ i=1; while(i<=10){print i; i++} }'
1
2
3
4
5
6
7
8
9
10
[root@web01 ~]# awk -F: '{i=1; while(i<=NF) {print $i; i++}}' /etc/passwd
[root@web01 ~]# awk -F: '{i=1; while(i<=10) {print $0; i++}}' /etc/passwd
[[root@web01 ~]# cat b.txt 
111 222
333 444 555
666 777 888 999
[root@web01 ~]# awk '{i=1;while(i<=NF) {print $i; i++}}' b.txt
111
222
333
444
555
666
777
888
999
# 1.8.2 for 循环

for 循环:for (初始化计数器;计数器测试;计数器变更) 动作

[root@web01 ~]# awk 'BEGIN{for(i=1;i<=5;i++){print i} }'
1
2
3
4
5
#将每行打印 10 次
[root@web01 ~]# awk -F: '{for(i=1;i<=10;i++) {print $0} }' /etc/passwd
[root@web01 ~]# awk -F: '{for(i=1;i<=NF;i++) {print $i} }' /etc/passwd
# 1.8.3 循环场景示例

需求:计算 1+2+3+4+...+100 的和,请使用 while、for 两种循环方式实现

# while 循环
[root@web01 ~]# cat awk_while_count.awk 
BEGIN {
	while ( i<=100 ) {
		sum=sum+i
		i++
	}
	print sum
}
[root@web01 ~]# awk -f awk_while_count.awk 
5050
# for 循环
[root@web01 ~]# cat awk_for_count.awk 
BEGIN {
	sum=0
	for (i=1;i<=100;i++) {
		sum=sum+i
		#sum+=i
	}
	print sum
}
[root@web01 ~]# awk -f awk_for_count.awk 
5050

# 2.Awk 数组

# 2.1 什么是 awk 数组
  • 数组其实也算是变量,传统的变量只能存储一个值,但数组可以存储多个值。
  • 不区分 关联数组 普通数组;
# 2.2 awk 数组应用场景

通常用来统计、比如:统计网站访问 TOP10、网站 Url 访问 TOP10 等等等

# 2.3 awk 数组统计技巧

1. 在 awk 中,使用数组时,不仅可以使用 1 2 3 ..n 作为数组索引,也可以使用字符串作为数组索引。

2. 要统计某个字段的值,就将该字段作为数组的索引,然后对索引进行遍历。

  • 1. 统计谁旧将谁作为索引的名称;
  • 2. 然后让其相同的进行自增;
  • 3. 遍历索引名称,获取对应出现的值,也就是次数;
# 2.4 awk 数组的语法

语法:array_name [index]=value

1. 统计 /etc/passwd 中各种类型 shell 的数量。

[root@web01 awk]# cat awk_passwd_type.awk 
BEGIN {
	FS=":"
}
# 1. 赋值 (没有写 while 循环,是因为 awk 本身就是循环读入文件)
{
	shell[$NF]++
}
#2. 循环玩所有的文件后,才会取执行 END 阶段; 
# shell 循环它,就是在循环数组的索引名称
END {
	for ( i in shell) {
		print i,shell[i]
	}
}
[root@web01 awk]# awk -f awk_passwd_type.awk /etc/passwd
/bin/sync 1
/bin/bash 8
/sbin/nologin 22
/sbin/halt 1
/sbin/shutdown 1

2. 统计主机上所有的 tcp 链接状态数,按照每个 tcp 状态分类。

[root@web01 awk]# netstat -an | grep tcp | awk '{arr[$6]++} END{for (i in arr) print i,arr[i]}'
LISTEN 8
ESTABLISHED 1
# 2.5 Awk 数组 - Nginx 日志示例

使用 awk 完成对 Nginx 的日志分析,日志格式如下:

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status$body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
[root@web01 nginx]# cat access.1w_oldxu.log |head -n 5
183.162.52.7 - - [10/Nov/2016:00:01:02 +0800] "POST /api3/getadv HTTP/1.1" 200 813 "www.ixuyong.com" "-" cid=0&timestamp=1478707261865&uid=2871142&marking=androidbanner&secrect=a6e8e14701ffe9f6063934780d9e2e6d&token=f51e97d1cb1a9caac669ea8acc162b96 "ixuyong/5.0.0 (Android 5.1.1; Xiaomi Redmi 3 Build/LMY47V),Network 2G/3G" "-" 10.100.134.244:80 200 0.027 0.027
10.100.0.1 - - [10/Nov/2016:00:01:02 +0800] "HEAD / HTTP/1.1" 301 0 "117.121.101.40" "-" - "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.16.2.3 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" - - - 0.000
117.35.88.11 - - [10/Nov/2016:00:01:02 +0800] "GET /article/ajaxcourserecommends?id=124 HTTP/1.1" 200 2345 "www.ixuyong.com" "http://www.ixuyong.com/code/1852" - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36" "-" 10.100.136.65:80 200 0.616 0.616
182.106.215.93 - - [10/Nov/2016:00:01:02 +0800] "POST /socket.io/1/ HTTP/1.1" 200 94 "chat.ixuyong.com" "-" - "android-websockets-2.0" "-" 10.100.15.239:80 200 0.004 0.004
10.100.0.1 - - [10/Nov/2016:00:01:02 +0800] "HEAD / HTTP/1.1" 301 0 "117.121.101.40" "-" - "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.16.2.3 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" - - - 0.000
# 2.5.1 统计访问地址前 TOP10
[root@web01 nginx]# cat nginx_top_10_1 
{
	cip[$1]++
}
END {
	for ( i in cip ) {
		print cip[i],i
	}
}
[root@web01 nginx]# awk -f nginx_top_10_1 access.1w_oldxu.log |sort -rn |head -10
4714 10.100.0.1
94 122.234.145.54
56 171.34.14.120
51 115.34.187.133
50 58.61.60.183
48 27.38.56.2
44 119.131.143.179
43 58.217.137.221
39 117.174.26.235
37 223.73.113.60
# 2.5.2 统计访问页面前 TOP10
[root@web01 nginx]# cat nginx_page_10_1 
{
	page[$7]++
}
END {
	for ( i in page ) {
		print page[i],i
	}
}
[root@web01 nginx]# awk -f nginx_page_10_1 access.1w_oldxu.log |sort -rn | head -10
4733 /
746 /course/ajaxmediauser/
467 /course/ajaxmediauser
307 /socket.io/1/
132 /u/card
109 /favicon.ico
103 /api3/savemediafinish
96 /api3/getinvitemeuserlist
94 /api3/latest
93 /u/loading
# 2.5.3 统计访问次数大于 50 的 IP
[root@web01 nginx]# cat nginx_top_10_2 
{
	cip[$1]++
}
END {
	for ( i in cip ) {
		if ( cip[i] > 50 ) {
			print cip[i],i
		}
	}
}
[root@web01 nginx]# awk -f nginx_top_10_2 access.1w_oldxu.log |sort -rn
4714 10.100.0.1
94 122.234.145.54
56 171.34.14.120
51 115.34.187.133
# 2.5.4 统计每个 URL 访问内容总大小
[root@web01 nginx]# cat nginx_page_total 
{
	request[$7]+=$10
	count[$7]++
}
END {
	for ( i in request ) {
		print request[i]/1024"kb",i,count[i]
	}
}
[root@web01 nginx]# awk -f nginx_page_total access.1w_oldxu.log |sort -rn |head -10
6144kb /f6e16e5d-ad78-42aa-acff-8af555fc4580/H.mp4 12
5632kb /392d975e-ba78-4990-a30e-0b66c2dd5a03/H.mp4 11
3072kb /894a9b9a-7ff0-4f2e-b46e-5c2f5b4b4311/M.mp4 6
2602.81kb / 4733
1697.21kb /static/module/class/content/img/49/section.png 1
1295.42kb /582133e8000111a404680172.jpg 2
1058.53kb /m/build/bundle.js 8
1024kb /c3302c4e-54c8-42b5-8c7a-0ca642d9b106/L.mp4 2
1024kb /b94b4de3-e0d4-4e05-b568-d4f4e51ae15e/M.mp4 2
1024kb /b3631c9e-0c5b-44ff-b7f5-59d9704267fb/L.mp4 2
# 2.5.5 统计状态码为 404 出现的次数
[root@oldxu ~]# cat ngx_status_top_404
{
	status[$9]++
}
END{
	for ( item in status) {
		if (item == 404 ) {
			print item,status[item]
		}
	}
}
[root@web01 nginx]# cat ngx_status_top_404 
{
	status[$9]++
}
END{
	for ( item in status) {
		if (item == 404 ) {
			print item,status[item]
		}
	}
}
[root@web01 nginx]# awk -f ngx_status_top_404 access.1w_oldxu.log 
404 77
# 2.6 Awk 数组 - MySQL 日志示例
# 2.6.1 模拟数据脚本

1. 模拟生产环境数据脚本,需要跑大约 30~60s (等待一段时间 ctrl+c 结束即可 *)*

[root@web01 nginx]# cat insert.sh 
#!/bin/bash
function create_random()
{
    min=$1
    max=$(($2-$min+1))
    num=$(date +%s%N)
    echo $(($num%$max+$min))
}
INDEX=1
while true
do
    for user in oldxu xiaowang xiaohong xiaoming xiaoqiang xiaoxiao
    do
        COUNT=$RANDOM
        NUM1=`create_random 1 $COUNT`
        NUM2=`expr $COUNT - $NUM1`
        echo "`date '+%Y-%m-%d %H:%M:%S'` $INDEX user: $user insert $COUNT records into datebase:product table:detail, insert $NUM1 records successfully,failed $NUM2 records" >> ./db.log.`date +%Y%m%d`
        INDEX=`expr $INDEX + 1`
    done
done

数据格式如下:

[root@web01 nginx]# cat db.log.20210928 | head -10
2021-09-28 15:34:09 1 user: oldxu insert 27947 records into datebase:product table:detail, insert 7129 records successfully,failed 20818 records
2021-09-28 15:34:09 1 user: oldxu insert 27947 records into datebase:product table:detail, insert 7129 records successfully,failed 20818 records
2021-09-28 15:34:09 2 user: xiaowang insert 16074 records into datebase:product table:detail, insert 3197 records successfully,failed 12877 records
2021-09-28 15:34:09 3 user: xiaohong insert 18478 records into datebase:product table:detail, insert 13383 records successfully,failed 5095 records
2021-09-28 15:34:09 4 user: xiaoming insert 18002 records into datebase:product table:detail, insert 829 records successfully,failed 17173 records
2021-09-28 15:34:09 5 user: xiaoqiang insert 17678 records into datebase:product table:detail, insert 10832 records successfully,failed 6846 records
2021-09-28 15:34:09 6 user: xiaoxiao insert 9778 records into datebase:product table:detail, insert 1700 records successfully,failed 8078 records
2021-09-28 15:34:09 7 user: oldxu insert 9117 records into datebase:product table:detail, insert 4888 records successfully,failed 4229 records
2021-09-28 15:34:09 8 user: xiaowang insert 17468 records into datebase:product table:detail, insert 904 records successfully,failed 16564 records
2021-09-28 15:34:09 9 user: xiaohong insert 27025 records into datebase:product table:detail, insert 5661 records successfully,failed 21364 records
$5 用户名称
$7 插入数据的总的次数 8302
$13 有多少条数据插入成功 1166
$16 有多少条数据插入失败 7136
array[$5]+=$7
success[$5]+=$13
failed[$5]+=$16
# 2.6.2 awk 数组实践

需求 1:统计每个人分别插入了多少条 records 进数据库

[root@web01 nginx]# cat db_1 
{
	total_insert[$5]+=$7
	#插入次数
	count[$5]++
}
END {
	for ( i in total_insert ) {
		print i,total_insert[i],count[i]
	}
}
[root@web01 nginx]# awk -f db_1 db.log.20210928 
xiaoxiao 94883429 5749
xiaoming 94932475 5749
xiaohong 95591046 5749
xiaoqiang 94070766 5749
oldxu 93651798 5750
xiaowang 93964965 5749

需求 2:统计每个人分别插入成功了多少 record,失败了多少 record

root@web01 nginx]# cat db_2
{
	total[$5]+=$7
	success[$5]+=$13
	failed[$5]+=$16
}
END {
	for ( i in success ) {
		print i,total[i],success[i],failed[i]
	}
}
[root@web01 nginx]# awk -f db_2 db.log.20210928 
xiaoxiao 94883429 46698127 48185302
xiaoming 94932475 47092399 47840076
xiaohong 95591046 48906546 46684500
xiaoqiang 94070766 47014395 47056371
oldxu 93651798 46668476 46983322
xiaowang 93964965 46679555 47285410

需求 3:将需求 1 和需求 2 结合起来,一起输出,输出每个人分别插入多少条数据,多少成功,多少失败,并且要格式化输出,加上标题

[root@web01 nginx]# cat db_3
BEGIN {
	printf "%-10s%-20s%-20s%-20s\n", 
	"User","Total", "Success", "Failed"
}
{
	total[$5]+=$7
	success[$5]+=$13
	failed[$5]+=$16
}
END {
	for ( i in success ) {
		printf "%-10s%-20d%-20d%-20d\n", i,total[i],success[i],failed[i]
	}
}
[root@web01 nginx]# awk -f db_3 db.log.20210928 
User      Total               Success             Failed              
xiaoxiao  94883429            46698127            48185302            
xiaoming  94932475            47092399            47840076            
xiaohong  95591046            48906546            46684500            
xiaoqiang 94070766            47014395            47056371            
oldxu     93651798            46668476            46983322            
xiaowang  93964965            46679555            47285410

需求 4:在例子 3 的基础上,加上结尾,统计全部插入记录数,成功记录数,失败记录数。

[root@web01 nginx]# cat db_4
BEGIN {
	printf "%-10s%-20s%-20s%-20s\n", 
	"User","Total", "Success", "Failed"
}
{
	total[$5]+=$7
	success[$5]+=$13
	failed[$5]+=$16
	# 变量
	total_number+=$7
	success_number+=$13
	failed_number+=$16
	
}
END {
	for ( i in success ) {
		printf "%-10s%-20d%-20d%-20d\n", i,total[i],success[i],failed[i]
	}
	printf "%-10s%-20d%-20d%-20d \n", "zongji",total_number,success_number,failed_number
}
[root@web01 nginx]# awk -f db_4 db.log.20210928 
User      Total               Success             Failed              
xiaoxiao  94883429            46698127            48185302            
xiaoming  94932475            47092399            47840076            
xiaohong  95591046            48906546            46684500            
xiaoqiang 94070766            47014395            47056371            
oldxu     93651798            46668476            46983322            
xiaowang  93964965            46679555            47285410            
zongji    567094479           283059498           284034981
此文章已被阅读次数:正在加载...更新于

请我喝[茶]~( ̄▽ ̄)~*

Xu Yong 微信支付

微信支付

Xu Yong 支付宝

支付宝