一、awk简介
awk是一种编程语言,用于linux/unix下对于文本和数据进行处理,数据可以来自于标准输入、一个或多个文件,或是其他命令的输出。
awk处理文本和数据的方式:逐行扫描文件,默认是从第一行到最后一行,寻找匹配的行,并执行相应的操作
awk可以用"统计数据",如网站的访问量、访问ip量…;支持条件判断;支持for和while循环;正则表达式。
二、使用方式
awk有两种使用方式:命令行模式和脚本模式
命令行模式
语法:
awk 选项 ‘命令部分’ 文件名
如果要引用shell变量需用双引号引起
常用的选项介绍
-F 定义字段分割符号,默认的分隔符是"空格"
-v 定义变量并赋值
awk内置变量
awk中BEGIN…END使用
BEGIN:表示在程序开始前执行
END :表示所有文件处理完后执行
用法:'BEGIN{开始处理之前};{处理中};END{处理结束后}'
awk使用基础:
[root@local tmp]# cat a.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@local tmp]# awk 'NR==5' a.txt **输出a.txt的第5行**
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
[root@local tmp]# awk 'NR==5,NR==7' a.txt 输出a.txt的第5到7行
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
[root@local tmp]# awk '/root/' a.txt 过滤出包含root的行
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@local tmp]# awk -F: 'NR==5{print $1,$NF}' a.txt 指定分隔符为:输出第5行第1个字段和最后一给字段的值
lp /sbin/nologin
[root@local tmp]# awk -F: 'NR==5,NR==7{print $1,$NF}' a.txt 指定分隔符为:输出第5行到第7行第1个字段和最后一给字段的值
lp /sbin/nologin
sync /bin/sync
shutdown /sbin/shutdown
[root@local tmp]# awk -F: '/root/{print $1,$NF}' a.txt 过滤出包含root行的第个字段和最后一个字段的值
root /bin/bash
operator /sbin/nologin
[root@local tmp]# awk -F: '/^root/;/^lp/{print $1,$NF}' a.txt 输出以root开头和以lp开头行的第1列和最后一列
root:x:0:0:root:/root:/bin/bash
lp /sbin/nologin
[root@local tmp]# awk -F: '/^root/,/^lp/{print $1,$NF}' a.txt 输出以root开头到以lp开头行的第1列和最后一列
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
内置变量分隔符举例(FS和OFS):
[root@local tmp]# awk 'BEGIN{FS="/"};/^root/;/^lp/{print $1,$NF}' a.txt 指定分隔符为/,输出以root开头和以lp开头行的第1列和最后一列
root:x:0:0:root: bash
lp:x:4:7:lp: nologin
[root@local tmp]# awk 'BEGIN{FS=":"};/^root/;/^lp/{print $1,$NF}' a.txt 指定分隔符为/,输出以root开头和以lp开头行的第1列和最后一列。这种写发和awk -F: 结果式一样的
root /bin/bash
lp /sbin/nologin
awk -F: 'BEGIN{OFS="@@@"};/^root/,/^lp/{print $1,$NF}' a.txt 设置分隔符为:,输出的分隔符为@@,以root开头和以lp开头行的第1列和最后一列
root@@/bin/bash
bin@@/sbin/nologin
daemon@@/sbin/nologin
adm@@/sbin/nologin
lp@@/sbin/nologin
awk的使用进阶:
printf相当于echo -n
%s 字符类型 strings
%d 数值类型
- 表示左对齐,默认是右对齐
printf默认不会在行尾自动换行,加\n
演示:
[root@local tmp]# awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' a.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变量定义:
awk -v 变量名=变量值 ‘awk语句’ 文件名
[root@local tmp]# awk -v num=5 'BEGIN{print num}' 设置num的值为5,并打印awk的值
5
[root@local tmp]# awk -v num=7 -F: '{print $num}' a.txt 设置num的值为7,并于$连用,表示第7列单元
/bin/bash
/sbin/nologin
/sbin/nologin
/sbin/nologin
/sbin/nologin
/bin/sync
/sbin/shutdown
/sbin/halt
/sbin/nologin
/sbin/nologin
BEGIN…END…实例
#在awk语句执行之前打印,执行后在打印。
[root@local tmp]# awk -F: 'BEGIN{ print "Login_user\t\tLogin_shell\n****************************"};NR==1,NR==5{print $1"\t\t"$NF};END{print "****************************"}' a.txt
Login_user Login_shell
****************************
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
****************************
#相比较上面一个命令,结果格式化了输出
[root@local tmp]# awk -F: 'BEGIN{print"u_name\t\th_dir\t\tshell\n***************************"};NR==1,NR==5{printf "%-20s %-20s %-20s\n",$1,$(NF-1),$NF};END{print "****************************"}' a.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
****************************
awk和正则的综合运用
运算符 说明
`== 等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于
~ 匹配
!~ 不匹配
! 逻辑非
&& 逻辑与
\|\| 逻辑或 `
实例
从第一行开始匹配到以lp开头行
awk -F: 'NR==1,/^lp/{print $0 }' a.txt
从第一行到第5行
awk -F: 'NR==1,NR==5{print $0 }' a.txt
从以lp开头的行匹配到第10行
awk -F: '/^lp/,NR==10{print $0 }' a.txt
从以root开头的行匹配到以lp开头的行
awk -F: '/^root/,/^lp/{print $0}' a.txt
打印以root开头或者以lp开头的行
awk -F: '/^root/ || /^lp/{print $0}' a.txt
awk -F: '/^root/;/^lp/{print $0}' a.txt
显示5-10行
awk -F: 'NR>=5 && NR<=10 {print $0}' a.txt
awk -F: 'NR<10 && NR>5 {print $0}' a.txt
打印1-9行以root开头的内容:
awk 'NR>=1 && NR<=9 && $0 ~ /^root/{print $0}' a.txt
awk 'NR>=1 && NR<=9 && /^root/' a.txt
显示可以登录系统的用户名
[root@local tmp]# awk -F: '$0 ~ /\/bin\/bash/{print $1}' /etc/passwd
root
yyy
zzz
打印出系统中普通用户的UID和用户名
[root@local tmp]# awk -F: 'BEGIN{print "UID\tUSERNAME"} {if($3>=1000 && $3 !=65534 ) {print $3"\t"$1} }' /etc/passwd
UID USERNAME
1001 yyy
1002 zzz
awk的脚本编程
㈠ 流程控制语句
① if结构
格式:
{ if(表达式){语句1;语句2;…}}
[root@local tmp]# awk -F: '{if($3>=1000 && $3<=60000) {print $1,$3} }' /etc/passwd
yyy 1001
zzz 1002
② if…else结构
格式:
{if(表达式){语句;语句;…}else{语句;语句;…}}
通过if..else结构判断用户是否是普通用户
awk -F: '{ if($3>=500 && $3 != 65534) {print $1"是普通用户"} else {print $1,"不是普通用户"}}' b.txt
③ if…elif…else结构
格式:
{ if(表达式1){语句;语句;…}else if(表达式2){语句;语句;…}else if(表达式3){语句;语句;…}else{语句;语句;…}}
# if...elif...else判断用户的类型
awk -F: '{ if($3==0) {print $1,":是管理员"} else if($3>=1 && $3<=999 || $3==65534 ) {print $1,":是系统用户"} else {print $1,":是普通用户"}}' /etc/passwd
#通过if...elif...else统计用户类型的个数
awk -F: '{ if($3==0) {i++} else if($3>=1 && $3<=999 || $3==65534 ) {j++} else {k++}};END{print "管理员个数为:"i "\n系统用户个数为:"j"\n普通用户的个数为:"k }' /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
(二)、 循环语句
① for循环
#打印1~5
awk 'BEGIN { for(i=1;i<=5;i++) {print i} }'
for ((i=1;i<=5;i++));do echo $i;done|awk '{print $0}'
awk 'BEGIN{ for(i=1;i<=5;i++) {print i} }'
awk 'BEGIN{ for(i=1;i<=5;i++) 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循环
#打印1~5
i=1;while (($i<=5));do echo $i;let i++;done
awk 'BEGIN { i=1;while(i<=5) {print i;i++} }'
awk 'BEGIN{i=1;while(i<=5) {print i;i++} }'
计算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 }'
③ 嵌套循环
普通的shell脚本实现
#!/bin/bash
for ((y=1;y<=5;y++))
do
for ((x=1;x<=$y;x++))
do
echo -n $x
done
echo
done
使用awk实现
awk 'BEGIN{ for(y=1;y<=5;y++) {for(x=1;x<=y;x++) {printf x} ;print } }'
awk 'BEGIN{ y=1;while(y<=5) { for(x=1;x<=y;x++) {printf x};y++;print}}'
break 条件满足的时候跳出循环
continue 条件满足的时候跳过当次循环
awk统计案例
#统计/etc/passwd中各种bash的数量
awk -F: '{ shells[$NF]++ };END{for (i in shells) {print i,shells[i]} }' /etc/passwd
#统计网站访问状态
ss -antp|grep 80|awk '{states[$1]++};END{for(i in states){print i,states[i]}}'
统计访问网站的每个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
统计网站日志中PV量
统计Apache/Nginx日志中某一天的PV量
grep '27/Jul/2017' mysqladmin.cc-access_log |wc -l
统计Apache/Nginx日志中某一天不同IP的访问量
grep '27/Jul/2021' mysqladmin.cc-access_log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' |sort -k2 -rn |head
grep '27/Jul/2021' access.log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' |awk '$2>100' |sort -k2 -rn