awk的基本用法

工作原理

逐行读取文本,默认以空格或tab键为分隔符进行分隔,将分隔所得的各个字段保存到内建变量中,并按模式或者条件执行编辑命令。 sed命令常用于一整行的处理而awk比较倾向于将一行分成多个"字段"然后再进行处理。awk信息的读入也是逐行读取的,执行结果可以通过print的功能将字段数据打印显示。在使用awk命令的过程中,可以使用逻辑操作符"&&"表示"与"、"||"表示"或"、"!"表示"非";还可以进行简单的数学运算

&& || + - * / % ^
取余 乘方
命令格式
awk 选项 '模式或条件 {操作}' 文件1 文件2 ……
awk -f 脚本文件 文件1 文件2 ……
awk常见的内建变量(可直接用)如下所示:
FS:列分割符。指定每行文本的字段分隔符,默认为空格或制表位。与"-F"作用相同
RS:行分隔符。awk从文件上读取资料时,将根据RS的定义把资料切割成许多条记录,而awk一次仅读入一条记录,以进行处理。预设值是'\n'
NF:当前处理的行的字段个数。
NR:当前处理的行的行号(序数)。
$0:当前处理的行的整行内容。
$n:当前处理行的第n个字段(第n列)。
FILENAME:被处理的文件名。

按字段输出文本

#输出第一到第三行
[root@localhost ~]# awk 'NR==1,NR==3 {print $0}' one.sh 
one
two
three
[root@localhost ~]# awk '(NR>=1)&&(NR<=3) {print $0}' one.sh 
one
two
three
#只输出第一行第三行
[root@localhost ~]# awk 'NR==1||NR==3 {print $0}' one.sh 
one
three
#求奇数行
[root@localhost ~]# awk '(NR%2)==1 {print}' one.sh 
one
three
five
seven
nine
eleven
[root@localhost ~]# for b in $(cat one.sh)
> do
> if [ $[a%2] -eq 1 ];then
> echo $b
> fi
> let a++
> done
one
three
five
seven
nine
eleven
#求偶数行
[root@localhost ~]# awk '(NR%2)==0 {print}' one.sh 
two
four
six
eight
ten
twelve
#通过字符来查找以t为开头的行文本内容
[root@localhost ~]# awk '/^t/ {print}' one.sh 
two
three
ten
twelve
#通过字符来查找/etc/passwd文件中以bash为结尾的行
[root@localhost ~]# awk '/bash$/ {print}' /etc/passwd 
root:x:0:0:root:/root:/bin/bash
linux-5:x:1000:1000:Linux-5:/home/linux-5:/bin/bash
#awk查找/etc/passwd文件中所有以/bin/bash结尾的行,然后执行x+1的操作从而统计出来
BEGIN模式表示,在处理指定的文本之前,需要先执行BEGIN模式中指定的操作;
awk再处理指定的文本,之后再执行END模式中指定的操作;
END{}语句块中,往往会放入打印结果等语句
[root@localhost ~]# awk 'BEGIN {x=0};/\/bin\/bash$/{x++};END {print x}' /etc/passwd 
2
#统计以/bin/bash结尾的行数,等同于grep -c "/bin/bash$" /etc/passwd

awk 'NR==1 {print $1}' one.sh #输出文本中第一行第一列的内容
awk 'NR==1 {print NF}' one.sh #输出多少列
awk 'NR==1 {print $NF}' one.sh #输出最后一列内容
指定列操作符:
#输出UID号大于等于1000的行号
awk -F ":" '$3>=1000 {print $1,$3}' /etc/passwd
#输出UID号不小于1000的行号
awk -F ":" '!($3<1000) {print $1,$3}' /etc/passwd
或
awk 'BEGIN {FS=":"};{if($3>=1000){print $1,$3}}' /etc/passwd
awk -F ":" '{max=($3>=$4)?$3:$4;{print max}}' /etc/passwd
#($3>$4)?$3:$4;三元运算符,如果第3个字段的值大于等于第4个字段的值,则把第3个字段的值赋给max,否则第4个字段的值赋给max

#打印出所有内容并且输出行号
[root@localhost ~]# awk -F: '{print NR,$0}' one.sh 
1 one
2 two
3 three
4 four
5 five
6 six
7 seven
8 eight
9 nine
10 ten
11 eleven
12 twelve

#打印出/etc/passwd中所有带有root的行数并且输出行号,输出每行内容和行号,每处理完一条记录,NR值加1
[root@localhost ~]# awk -F: '/root/{print NR,$0}' /etc/passwd
1 root:x:0:0:root:/root:/bin/bash
10 operator:x:11:0:operator:/root:/sbin/nologin
#打印出带有/etc/passwd文件中带有bash的用户名,输出以冒号分隔且第7个字段中包含/bash的行的第1个字段
[root@localhost ~]# awk -F: '$7~"bash"{print NR,$1}' /etc/passwd
1 root
41 linux-5
#输出第1个字段中包含root且有7个字段的行的第1、2个字段
[root@localhost ~]# awk -F ":" '($1~"root")&&(NF==7) {print $1,$2}' /etc/passwd
root x
#输出第7个字段既不为/bin/bash,也不为/sbin/nologin的所有行
[root@localhost ~]# awk -F: '($NF!="/bin/bash")&&($7!="/sbin/nologin") {print}' /etc/passwd
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
通过管道、双引号调用Shell命令
#统计以冒号分隔的文本段落数,END{}语块中,往往会放入打印结果等语句
[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost ~]# echo $PATH | awk 'BEGIN {RS=":"}; {print NR}'
1
2
3
4
5
[root@localhost ~]# echo $PATH | awk 'BEGIN {RS=":"}; END {print NR}'
5
#调用wc -l命令统计使用bash的用户个数,等同于grep -c "/bin/bash$" /etc/passwd
[root@localhost ~]# awk -F: '/bash$/ {print | "wc -l"}' /etc/passwd
2
相当于:
[root@localhost ~]# awk -F: '/bash$/ {print}' /etc/passwd | wc -l
2
#查看当前内存使用百分比
[root@localhost ~]# free | awk  '/Mem:/{print $3/$2*100"%"}'
14.866%
取整数值
[root@localhost ~]# free | awk  '/Mem:/{print int($3/$2*100)"%"}'
14%
#查看cup的使用率空闲率,top -b -n 1用于获取一次静态的top结果,方便数据的截取
[root@localhost ~]# top -b -n 1 | awk -F, '/Cpu/{print $4}' | awk '{print $1}'
100.0

date命令用法

#显示年月日
[root@localhost ~]# date +"%Y/%m/%d %H:%M:%S"
2021/12/27 09:40:40
#这个月的第一天
[root@localhost ~]# date +%Y%m01
20211201
#上个月的最后一天
[root@localhost ~]# date -d "$(date +%Y%m01) -1 day" +"%Y%m%d"
20211130
[root@localhost ~]# date -d "$(date +%Y%m01) -1 day" +"%F"
2021-11-30
#这个月的最后一天
[root@localhost ~]# date -d "$(date -d "1 month" +%Y%m01) -1 day" +%F
2021-12-31

#显示上次系统重启时间,等同于uptime;second ago为显示多少秒前的时间
[root@localhost ~]# date -d "$(cat /proc/uptime | awk -F. '{print $1}') second ago" +"%F %H:%M:%S"
2021-12-27 08:34:51
#调用w命令,并用来统计在线用户数
[root@localhost ~]# awk 'BEGIN {n=0 ; while ("w" | getline) n++ ; {print n-2}}'
1
#调用hostname并输出当前主机名
[root@localhost ~]# awk 'BEGIN {"hostname" | getline ; {print $0}}'
localhost.localdomain
使用getline命令会把相应的结果获取并且把值赋给后面的"$0"

getline命令

#获取偶数行
[root@localhost ~]# seq 10 | awk '{getline; print $0}' 
#awk先读取第一行内容,然后getline会换一行然后通过print $0打印出来
2
4
6
8
10
#获取奇数行
[root@localhost ~]# seq 10 | awk '{print $0; getline}'
#awk读取第一行内容,通过print $0打印出来,getline会换一行但是后面没有打印操作所以不进行操作
1
3
5
7
9
当getline左右无重定向符“<”或“|”时,awk首先读取到了第一行,就是1,然后getline, 就得到了1下面的第二行,就是2,因为getline之后,awk会改变对应的NF,NR, FNR和$0等内部变量,所以此时的$0的值就不再是1,而是2了,然后将它打印出来。
当getline左右有重定向符“<”或“|”时,getline则作用于定向输入文件,由于该文件是刚打开,并没有被awk读入一行,只是getline读入,那么getline返回的是该文件的第一行,而不是隔行。
$0表示整行内容
NF:当前处理的行的字段个数。
NR:当前处理的行的行号(序数)
FNR:表示awk当前读取的记录数

OFS指定分隔符

[root@localhost ~]# echo "A B C D" | awk 'BEGIN{OFS="|"};{print $1 $2 $3 $4}'
ABCD
[root@localhost ~]# echo "A B C D" | awk 'BEGIN{OFS="|"};{print $1,$2,$3,$4}'
A|B|C|D
[root@localhost ~]# echo "A B C D" | awk 'BEGIN{OFS="|"};{$1=$1;print $0}'
A|B|C|D
$1=$1是用来激活$0的重新赋值,也就是说
字段$1...和字段数NF的改变会促使awk重新计算$0的值,通常是在改变OFS后而需要输出$0时这样做
OFS:指定awk输出的列分隔符

awk的高级结合操作

[root@localhost ~]# cat test.txt 
aaa
aaa
bbb
ccc
aaa
bbb
aaa
#awk中可以把数组定义成字符串
[root@localhost ~]# awk 'BEGIN{a[0]=10; a[1]=20;print a[1]}'
20
[root@localhost ~]# awk 'BEGIN{a[0]=10; a[1]=20;print a[0]}'
10
[root@localhost ~]# awk 'BEGIN{a["abc"]=10; a["def"]=20;print a["abc"]}'
10
[root@localhost ~]# awk 'BEGIN{a["abc"]=10; a["def"]=20;print a["def"]}'
20
#但是Linux中不可以用字符串输出
[root@localhost ~]# arr["abc"]=10
[root@localhost ~]# arr["def"]=20
[root@localhost ~]# echo ${arr[@]}
20

[root@localhost ~]# awk 'BEGIN{a[0]=10; a[1]=20; a[2]=30; for(i in a){print i,a[i]}}'
0 10
1 20
2 30

BEGIN模式中的命令执行一次

awk中的数组的下标和元素的值都可以是数字也可以是字符串,字符串需要用双引号括起来

awk数组的数组名保存的是下标的列表,相对应的元素的值使用数组名[下标]来表示

#达到统计效果
[root@localhost ~]# awk '{a[$1]++}; END {for(i in a){print i,a[i]}}' test.txt
aaa 4
ccc 1
bbb 2
#再进行相应排序
[root@localhost ~]# awk '{a[$1]++}; END {for(i in a){print i,a[i]}}' test.txt | sort -t ' ' -k2 -nr
aaa 4
bbb 2
ccc 1
总结:
首先{a[$1]++}; $1代表的是文件的第一列内容,并且awk会逐行读取文件中的内容,a[$1]初始值为0,当awk匹配到一行相同内容后就会自增加1,也就是说 a[$1] 的值取决于$1在文件中的此列中出现的次数,而$1的值都保存在数组a中;
END {for(i in a){print i,a[i]}} END模式的操作是在awk处理完文件内容后再执行的操作;
for 循环中的变量 i 用来遍历获取数组a的值(也就是说是$1的值);print i 相当于把$1列中不同的值进行去重后输出;a[i]相当于把$1列中不同的值对应出现的次数进行输出。
#过滤出密码输错误3次的IP
[root@localhost ~]# awk '/Failed passwd/{ip[$11]++}; END {for(i in ip){print i,ip[i]}}' /var/log/secure-20211227 | awk '$2>3{print $1}'

监控脚本(监控系统各项指标、监控服务状态、硬件的信息、安全的监控)

cpu负载,内存容量,硬盘空间,网卡流量,安装的软件包数量,账户数量,当前登录的账户数量,进程数量,输错密码的主机
uptime,free -m,df -h,ifconfig ens33,rpm -qa | wc -l,/etc/passwd,who,ps aux,/var/log/secure
ip=`ifconfig ens33 | awk '/inet /{print $2}'`
echo "本地IP地址是:"$ip 
cpu= `uptime | awk ' {print $NF}'`
#awk中NF为当前行的列数,$NF是最后一列
echo"本机CPU最近15分钟的负载是:"$cpu
net_in=`ifconfig ens33 | awk '/RX p/{print $5}'`
echo "入站网卡流量为:"$net_in
net_out=`ifconfig ens33 | awk '/TX p/{print $5}'`
echo "出站网卡流量为:"$net_out
mem=`free -m | awk '/^Mem/{print $4)'`
echo "内存剩余容量为:" $mem
disk=`df -h | awk '/sda1/{print $4}'`
echo "根分区剩余容量为:"$disk
user=`cat /etc/passwd |wC -l`
echo "本地账户数量为:"$user
login=`who | wc -l`
echo "当前登陆计算机的账户数量为:"$login
process=`ps aux | wc -l`
echo "当前计算机启动的进程数量为: "$process
soft=`rpm-qa | wc -l`
echo "当前计算机已安装的软件数量为:"$soft

系统初始化脚本(创建目录,账户,安装软件吧,设置权限,修改内核参数,防火墙,selinux)

一键部署脚本(源码编译安装的脚本)

备份脚本(自动备份数据库,备份网址的日志数据,配置文件)

日志分析脚本(分析日志的数据,汇总统计相关信息,PV(网页的点击量),UV(访问的客户端数量))