基础语法
记录与字段
awk是一种处理文本文件的编程语言,文件的每行数据都被称为记录,默认以空格或制表符为分隔符,每条记录被分成若干字段(列),awk每次从文件中读取一条记录。
语法格式
awk [选项] '条件{动作} 条件{动作} ... ...' 文件名
##内置变量
awk语法由一系列条件和动作组成,在花括号内可以有多个动作,多个动作之间用分号分隔,在多个条件和动作之间可以有若干空格,也可以没有。
如果没有指定条件则匹配所有数据,如果没有指定动作则默认为print打印。
变量名 | 描述 |
---|---|
FILENAME | 当前输入文档的名称 |
FNR | 当前输入文档的当前行号,尤其当有多个输入文档时有用 |
NR | 输入数据流的当前行号 |
$0 | 当前行的全部数据内容 |
$n.... | 当前行的第n个字段的内容 |
NF | 当前记录(行)的字段(列)个数 |
FS | 字段分隔符,默认为空格或者Tab制表符 |
OFS | 输出字段分隔符,默认为空格 |
ORS | 输出记录分隔符,默认为换行符\n |
RS | 输入记录分隔符,默认为换行符\n |
ARGC | 命令行参数个数 |
ARGV | 命令行参数数组 |
OFMT | 数值的输出格式 |
# free | awk '{print $2}' #逐行打印第2列
used
3062364
0
$ free | awk '{print NR}' #输出行号
$ free | awk '{print NF}' #输出每行数据的列数
$ awk '{print $NF}' /etc/hosts #打印每行数据的最后一列
$ awk '{print $(NF-1)}' /etc/hosts #打印每行倒数第二列
$ awk '{print $0}' /etc/hosts #打印每行全部内容
$ cp /etc/hosts /tmp/hosts
$ awk '{print NR}' /tmp/hosts /etc/hosts
1
2
3
4
$ awk '{print FNR}' /tmp/hosts /etc/hosts
1
2
1
2
# 同样是输出行号,NR将所有文件的数据视为一个数据流,而FNR则是将多个文件的数据视为独立的若干个数据流,遇到新文件时行号从1开始重新递增。
自定义变量
$ awk -v x="bob" -v y=10 '{print x,y}' /tmp/hosts
bob 10
bob 10
调用系统变量
$ awk -v shell=$SHELL '{print shell}' /tmp/hosts
# 或者
$ awk '{print "'$SHELL'"}' /tmp/hosts #双引号加单引号组合能正确获取系统变量
/bin/bash
/bin/bash
自定义分隔符
默认以空格、换行符、制表符作为分隔符,使用-F可以指定分隔符
awk -F: '{print $1}' /etc/passwd #以冒号作为分隔符
awk -F"[:,_]" '{print $1}' /etc/passwd #使用集合定义分隔符
内置变量RS、OFS、ORS
RS
内置变量RS保存的是输入数据的行分隔符,默认为\n,可以指定其它字符作为行分隔符
awk -v RS="." '{print $1}' /tmp/hosts #指定.作为行分隔符
OFS
保存的是输出字段的分隔符(列分隔符),默认为空格
awk -v OFS="-" '{print $1,$2}' /tmp/hosts #以"-"作为字段分隔符
awk -v OFS="\t" '{print $1,$2}' /tmp/hosts #以Tab制表符为字段分隔符
awk -v OFS=". " '{print NR,$0}' /tmp/hosts #在每行前面加上行号和点
ORS
保存的是输出记录的分隔符
awk -v ORS="-" '{print $1}' /tmp/hosts
print指令
可以输出常量和变量,如果是字符串常量需要用双引号括起来,数字常量可以直接打印
awk '{print 123}' /tmp/hosts
awk '{print "IP:",$1}' /tmp/hosts
awk '{print "第1列:"$1,"\t第2列:"$2}' /tmp/hosts
条件匹配
awk支持使用正则进行模糊匹配,也支持字符串和数字的精确匹配,并且支持逻辑与和逻辑或。
比较符号 | 描述 |
---|---|
// | 全行数据正则匹配 |
!// | 对全行数据正则匹配后取反 |
~// | 对特定数据正则匹配 |
!~// | 对特定数据正则匹配后取反 |
== | 等于 |
!= | 不等于 |
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
&& | 逻辑与,如A&&B,要求满足A并且满足B |
|| | 逻辑或,如A||,要求满足A或者满足B |
awk '/localhost/' /tmp/hosts
awk '$3~/local/' /tmp/hosts #每行的第3列去匹配local
awk '$3~/local/{print $1,$2}' /tmp/hosts
awk '$2=="localhost"' /tmp/hosts #第2列精确匹配localhost
awk '$2!="localhost"' /tmp/hosts #取反
awk -F: '$3<=10' /etc/passwd #第3列小于等于10的行
awk -F: 'NR==10' /etc/passwd #仅显示第10行
awk -F: '$3>1 && $3<5' /etc/passwd #逻辑与
awk -F: '$3==1 || $3==5' /etc/passwd #逻辑或
BEGIN和END
BEGIN导致动作指令仅在读取任何数据记录之前执行一次,END导致动作指令仅在读取完所有数据记录后执行一次
BEGIN可以进行数据初始化,END可以进行数据汇总。
awk 'BEGIN{print "OK"}'
awk 'END{print NR}' /etc/passwd #打印最后一行的行号
awk -F: 'BEGIN{print "用户名 UID 解释器"} \
{print $1,$3,$7} \
END {print "共有"NR"个账号."}' /etc/passwd | column -t #column实现格式化输出,并按升序排序
数值计算
$ awk 'BEGIN{print 2+3}'
5
$ awk 'BEGIN{print 2*3}'
6
$ awk 'BEGIN{print 2/5}'
0.4
$ awk 'BEGIN{print 5%2}'
1
$ awk 'BEGIN{print 5**2}'
25
$ awk 'BEGIN{x=5;y=2;print x-y}'
3
$ awk 'BEGIN{x=1;x++;print x}'
2
$ awk 'BEGIN{x=1;x+=1;print x}'
2
awk中变量不需定义就可以直接使用,作为字符处理时未定义的变量默认值为空,作为数字处理时未定义的变量默认值为0
$ awk 'BEGIN{print "["x"]","["y"]"}' #x和y默认为空
[] []
$ awk 'BEGIN{print x+8}' #x默认为0
8
循环计数
awk '/bash$/{x++} END{print x}' /etc/passwd
逐行读取/etc/passwd文件,x初始值为0,匹配到以bash结尾的行时自加1,最后打印x的值。
who | awk '$1=="root"{x++} END{print x}' #统计有多少个客户端登录root
seq 200 | awk '$1%6==0 && $1~/6/' #打印1~200之间能被6整除且包含数字6的整数数字
awk条件判断
if判断后面如果只有一个动作指令,则花括号{}可省略,如果if判断后面的指令为多条指令则需要使用花括号括起来,多个指令使用分号分隔。
单分支语句
语法:
if(判断条件){
动作指令序列;
}
查找cpu使用率大于0.3的进程
ps -eo user,pid,pcpu,comm | awk '{if($3>0.5) print}'
双分支if语句
语法:
if(判断条件){
动作指令1;
} else {
动作指令2;
}
统计系统用户与普通用户的个数
awk -F: '{if($3<1000){x++} else{y++}} END{print "系统用户个数:"x"","普通用户个数:"y""}' /etc/passwd
多分支语句
语法:
if(判断条件){
动作指令1;
} else if(判断条件2){
动作指令2;
} else {
动作指令N;
}
awk数据与循环
$ awk 'BEGIN{a[0]=11;a[1]=12;print a[0],a[1]}'
awk 'BEGIN{a[0]=11;a[1]=12;print a[0],a[1]}'
$ awk 'BEGIN{ \
> tom["age"]=22; \
> tom["addr"]="sichuan"; \
> print tom["age"],tom["addr"] \
> }'
22 sichuan
遍历数组
语法:
for(变量 in 数组名){
动作指令序列
}
示例:
$ awk 'BEGIN{ \
> a[0]=1;a[11]=22;a["book"]=32;a["work"]="home"; \
> for(i in a){print i,a[i]} \
> }'
# 输出是无序的。i是索引,a[i]是值
book 32
work home
11 22
0 1
成员关系判断
$ awk 'BEGIN{ \
> a[11]=1;a[22]=2; \
> if("22" in a){print "yse"} else {print no} \
> }'
yse
for循环
采用与C语言一样的语法格式
语法:
for(表达式1;表达式2;表达式3) {
动作指令序列
}
示例:
$ awk 'BEGIN{ for (i=1;i<=4;i++) {print i}}'
1
2
3
4
# 统计root出现的次数。
# 这里面包含了两个循环,一个是隐含循环,awk会逐行处理数据;一个是for循环每列的值,如果等于root,就让x自加1,最后打印x的值
$ awk -F: '{ \
> for(i=1;i<=NF;i++) \
> {if($i=="root") x++} \
> } END {print x}' /etc/passwd
2
while循环
语法:
while(条件判断){
动作指令序列;
}
示例:
awk 'BEGIN{ i=1; while(i<=5) {print i;i++}}'
中断语句
与shell类似,awk提供了continue、break、exit循环中断语句。
$ awk 'BEGIN{ \
> i=0;
> while(i<=5) { \
> i++; \
> if(i==3) {continue}; \
> print i \
> }; \
> } \
> END {print "END"}' /tmp/hosts
1
2
4
5
6
END
awk函数
内置I/O函数
getline
能让awk立刻读取下一行数据(读取下一条记录并复制给$0,并重新设置NF、NR和FNR)
#解决挂载逻辑卷时,分区信息跨行显示的问题
df -h | awk '{if(NF==1) {getline;print $3}; if(NF==6) {print $4}}'
next
停止处理当前的输入记录,立刻读取下一条记录并返回awk程序的第一个模式匹配重新处理数据。
有点类似于循环语句中的continue,不会执行当次循环的后续语句
awk -F: '/root/{getline;print "next line:",$0} {print "normal line"}' /etc/passwd
awk -F: '/root/{next;print "next line:",$0} {print "normal line"}' /etc/passwd
经比较可以看出,getline,会继续执行后续的指令print “next line:”,而next不会执行后续指令,而是重新开始匹配
system()
可以直接在awk中调用shell命令,会启动一个新shell进程执行命令
awk 'BEGIN{system("ls")}'
awk '{system("echo date:"$0)}' /tmp/hosts
内置数值变量
int(expr)
可以对小数取整
$ awk 'BEGIN{print int(6.8)}'
6
rand()
返回0到1之间的随机数
$ awk 'BEGIN{print rand()}'
0.237788
$ awk 'BEGIN{for(i=1;i<=5;i++) print int(100*rand())}'#生成5个100以内的随机数
23
29
84
15
58
srand([])
可以使用expr定义新的随机数种子,没有expr时则使用当前系统的时间为随机数种子
awk 'BEGIN{srand();print rand()}' #使用时间做随机数种子
awk 'BEGIN{srand(22);print rand()}' #使用数值做随机数种子
内置字符串函数
length([s])
可以统计字符串s的长度,如果不指定字符串s则统计$0的长度
awk 'BEGIN{test="hello"; print length(test)}' #打印字符串长度
awk 'BEGIN{t[0]="hi";t[1]="the"; print length(t)}' #返回数组元素个数
awk '{print length()}' /etc/shells #返回文件每行的字符长度
index(字符串1,字符串2)
返回字符串2在字符串1中的位置
awk 'BEGIN{test="hello";print index(test,"l")}'
match(s,r)
根据正则表达式r返回其在字符串s中的位置坐标
$ awk 'BEGIN{print match("How much","[a-z]")}' #小写字母在第2个位置开始出现
2
tolower(srt)
可以将字符串转换为小写
$ awk 'BEGIN{print tolower("HELLo")}'
hello
toupper(str)
将字符串转为大写
split(字符串,数组,分隔符)
将字符串按特定的分隔符切片后存储在数组中,如果没指定分隔符,则使用IFS定义的。
数组下标从1开始
$ awk 'BEGIN{split("hello world",test); print test[1],test[2]}'
hello world
$ awk 'BEGIN{split("hello:world",test,":"); print test[1],test[2]}' #指定冒号(:)为分隔符
hello world
gsub(r,s,[,t])
将字符串t中所有与正则表达式r匹配的字符串全部替换为s,如果没有指定字符串t,则默认对$0进行替换操作
$ [15:11:47][root@localhost:~]# head -1 /etc/passwd | awk '{gsub("[0-9]","**");print $0}'
root:x:**:**:root:/root:/bin/bash
sub(r,s,[,t])
与gsub类似,但仅替换第一个匹配的字符串,而不是替换全部
substr(s,i,[,n])
对字符串s进行截取,从第i位开始,截取n个字符串,如果n没有指定则一直截取到字符串s的末尾位置
$ awk 'BEGIN{hi="Hello World"; print substr(hi,2,3)}' #从第2位开始截取3个字符
ell
内置时间函数
systime()
返回当前时间距离1970-01-01 00:00:00有多少秒
$ awk 'BEGIN{print systime()}'
1627802328
用户自定义函数
语法:
function 函数名(参数列表) { 命令序列 }
示例:
$ awk ' \
> function max(x,y) { \
> if(x>y) {print x} \
> else {print y} } \
> BEGIN {max(5,6)} '
6
常用例子
打印各磁盘可用大小
df | grep -v tmpfs | awk 'NR!=1 {disk[$1]=$4} \
END {for(i in disk) {printf "%-20s %-10s\n",i,disk[i]/1024"M"}
}'
统计磁盘可用容量
df | tail -n +2 | grep -v tmpfs | awk '{sum+=$4} END{print "磁盘可用容量:"sum/1024/1024"G"}'
统计/etc下文件总大小
ls -l /etc | awk '/^-/{sum+=$5} END{print "文件总大小:"sum/1024"M"}'
统计访问Nginx的各IP访问次数
awk ' \
{IP[$1]++} \
END { \
for (i in IP) {print i,IP[i]} \
}' /var/log/nginx/access.log
查看Nginx 1点到5点半的日志
awk -F"[: /]" '$7":"$8 >= "01:00" && $7":"$8 <="05:30"' /var/log/nginx/access.log
查看Docker容器的cpu使用率
docker stats jenkins --no-stream |awk 'NR==2{print $3}'