awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来格式化文本
awk [-F|-f|-v] 'BEGIN{ commands } pattern{ commands } END{ commands }' file
[-F|-f|-v] 大参数,-F指定分隔符,-f调用脚本,-v定义变量 var=value
' ' 引用代码块
BEGIN 初始化代码块,在对每一行进行处理之前,初始化代码,主要是引用全局变量,设置FS分隔符
pattern // 匹配代码块,可以是字符串或正则表达式
{} 命令代码块,包含一条或多条命令
; 多条命令使用分号分隔
END 结尾代码块,在对每一行进行处理之后再执行的代码块,主要是进行最终计算或输出结尾摘要信息
BEGIN{ commands } pattern{ commands } END{ commands }
第一部分:BEGIN{ commands }。
该语句块不依赖于文件,是在读取文件之前,执行该语句中的语句块,常用于变量的初始化,打印输出表格的表头。
第二部分:pattern{ commands }
它逐行从从文件或标准输入读取内容,从第一行到最后一行,每读取一行,执行一次pattern{ commands }语句块,如果没有pattern语句块,则默认运行{ print },即打印每个读取到的行
第三部分:END{ commands }
此语句是在文件所有行被读取完成后执行,常用于打印全部行的分析结果这类信息汇总都是在END语句块中完毕
说明:上面三个部分,至少要有一个
$0 表示整个当前行
$1 每行第一个字段
NF 字段数量变量
NR 每行的记录号,多文件记录递增
FNR 与NR类似,不过多文件记录不递增,每个文件都从1开始
\t 制表符
\n 换行符
FS BEGIN时定义分隔符
RS 输入的记录分隔符, 默认为换行符(即文本是按一行一行输入)
~ 匹配,与==相比不是精确比较
!~ 不匹配,不精确比较
== 等于,必须全部相等,精确比较
!= 不等于,精确比较
&& 逻辑与
|| 逻辑或
+ 匹配时表示1个或1个以上
/[0-9][0-9]+/ 两个或两个以上数字
/[0-9][0-9]*/ 一个或一个以上数字
FILENAME 文件名
OFS 输出字段分隔符, 默认也是空格,可以改为制表符等
ORS 输出的记录分隔符,默认为换行符,即处理结果也是一行一行输出到屏幕
print #print $1$2 无空格拼接$1$2, print $1,$2 会有输出分割符隔开$1和$2
]# awk -f script.awk file
]# cat script.awk
BEGIN{
FS=":"
}
{print $1}
//效果与awk -F":" '{print $1}'相同,只是分隔符使用FS在代码自身中指定
-F'[|-% ]+' :表示用[]里面的字符组合做分隔符
$1 指指定分隔符后,第一个字段,$3第三个字段, \t是制表符
一个或多个连续的空格或制表符看做一个定界符,即多个空格看做一个空格
awk -F":" '{print $1}' /etc/passwd
awk -F":" '{print $1 $3}' /etc/passwd //$1与$3相连输出,不分隔
awk -F":" '{print $1,$3}' /etc/passwd //多了一个逗号,$1与$3使用空格分隔
awk -F":" '{print $1 " " $3}' /etc/passwd //$1与$3之间手动添加空格分隔
awk -F":" '{print "Username:" $1 "\t\t Uid:" $3 }' /etc/passwd //自定义输出
awk -F: '{print NF}' /etc/passwd //显示每行有多少字段
awk -F: '{print $NF}' /etc/passwd //将每行第NF个字段的值打印出来
awk -F: 'NF==4 {print }' /etc/passwd //显示只有4个字段的行
awk -F: 'NF>2{print $0}' /etc/passwd //显示每行字段数量大于2的行
awk '{print NR,$0}' /etc/passwd //输出每行的行号
awk -F: '{print NR,NF,"\t",$NF}' /etc/passwd //依次打印行号,字段数,制表符,最后字段值
awk -F: 'NR==5{print}' /etc/passwd //显示第5行
awk -F: 'NR==5 || NR==6{print}' /etc/passwd //显示第5行和第6行
route -n|awk 'NR!=1{print}' //不显示第一行
]# echo "1 2|3 4|5"|awk -F'[|]+' '{print "$1="$1,"\n$2="$2,"\n$3="$3}'
$1=1 2
$2=3 4
$3=5
]# user="root"
]# awk -F: -vu=$user '$1==u' /etc/passwd #过滤出第一列是root的行
root:x:0:0:root:/root:/bin/bash
用~表示包含,!~表示不包含
打印出第一列包含root的行
]# awk -F: '$1~/roo/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
打印出最后一列不含bash,且不包含login的行
]# awk -F: '$NF!~/bash/ && $NF!~/login/' /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
//纯字符匹配 !//纯字符不匹配 ~//字段值匹配 !~//字段值不匹配 ~/a1|a2/字段值匹配a1或a2
awk '/mysql/' /etc/passwd
awk '/mysql/{print }' /etc/passwd
awk '/mysql/{print $0}' /etc/passwd #三条指令结果一样
awk '!/mysql/{print $0}' /etc/passwd #输出不匹配mysql的行
awk '/mysql|mail/{print}' /etc/passwd #输出匹配mysql或mail的行
awk '!/mysql|mail/{print}' /etc/passwd #输出不匹配mysql和mail的行
awk -F: '/mail/,/mysql/{print}' /etc/passwd #区间匹配,匹配mail的行到匹配mysql的行
awk '/[2][7][7]*/{print $0}' /etc/passwd #匹配包含27为数字开头的行,如27,277,2777...
awk -F: '$1~/mail/{print $1}' /etc/passwd #$1匹配指定内容才显示
awk -F: '{if($1~/mail/) print $1}' /etc/passwd #与上面相同,$1匹配指定内容才显示
awk -F: '$1!~/mail/{print $1}' /etc/passwd #$1不匹配包含mail的行
awk -F: '$1!~/mail|mysql/{print $1}' /etc/passwd #$1不匹配包含mail和mysql的行
== != > >=
awk -F":" '$1=="mysql"{print $3}' /etc/passwd
awk -F":" '{if($1=="mysql") print $3}' /etc/passwd //与上面相同
awk -F":" '$1!="mysql"{print $3}' /etc/passwd //不等于
awk -F":" '$3>1000{print $3}' /etc/passwd //大于
awk -F":" '$3>=100{print $3}' /etc/passwd //大于等于
awk -F":" '$3<1{print $3}' /etc/passwd //小于
awk -F":" '$3<=1{print $3}' /etc/passwd //小于等于
&& ||
awk -F: '$1~/mail/ && $3>8 {print }' /etc/passwd //逻辑与,$1匹配mail,并且$3>8
awk -F: '{if($1~/mail/ && $3>8) print }' /etc/passwd
awk -F: '$1~/mail/ || $3>1000 {print }' /etc/passwd //逻辑或
awk -F: '{if($1~/mail/ || $3>1000) print }' /etc/passwd
awk -F: '$3 > 100' /etc/passwd
awk -F: '$3 > 100 || $3 < 5' /etc/passwd
awk -F: '$3+$4 > 200' /etc/passwd
awk -F: '/mysql|mail/{print $3+10}' /etc/passwd //第三个字段加10打印
awk -F: '/mysql/{print $3-$4}' /etc/passwd //减法
awk -F: '/mysql/{print $3*$4}' /etc/passwd //求乘积
awk '/MemFree/{print $2/1024}' /proc/meminfo //除法
awk '/MemFree/{print int($2/1024)}' /proc/meminfo //取整
必须用在{}中,且比较内容用()扩起来
awk -F: '{if($1~/mail/) print $1}' /etc/passwd //单分支简写
awk -F: '{if($1~/mail/) {print $1}}' /etc/passwd //单分支全写
awk -F: '{if($1~/mail/) {print $1} else {print $2}}' /etc/passwd //双分支 if...else...
]# awk -F: -va=3 -vb=4 'BEGIN{if(a>b) print "a>b";else if(a==b) print "a==b";else print "a100 ? "yes":"no")}' /etc/passwd
awk -F: '{print ($3>100 ? $3":\tyes":$3":\tno")}' /etc/passwd
与shell类似,awk提供了在循环中使用continue、break、exit循环中断语句。
]# awk 'BEGIN{sum=0;i=1;while(i<5){if(i/2==0)continue;sum+=i;i++}; print sum}'
awk 'BEGIN{sum=0;i=1;do{sum+=i;i++}while(i<=5); print sum}'
]# awk 'BEGIN{sum=0;for(i=1;i<5;i++){sum+=i};print sum}'
15
]# awk 'BEGIN{sum=0;i=1;for(i=1;i<=5;i++){if(i%2==0)continue;sum+=i}; print sum}'
9
]# awk '{a[$1]++} END{for(i in a) print i,a[i]}' access.log|sort -k2 -nr
awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
//输出字段6匹配WAIT的行,其中输出每行行号,字段4,5,6,并使用制表符分割字段
]# echo "1 2 3"|awk '{NF=NF;print $0}' OFS="|" #NF=NF;加上此设置,就可以让$0以OFS分隔
1|2|3
①在命令代码块中直接输出 route -n|awk 'NR!=1{print > "./fs"}'
②使用重定向进行输出 route -n|awk 'NR!=1{print}' > ./fs
print : 打印
printf: 格式化打印
%: 格式化输出分隔符,每个%对应后面一个要打印的变量
%s: 字符串,
%d: 数字
-: 左对齐
+: 右对齐
15: 至少占用15字符
awk中函数(格式化打印)
案例1:
awk -F: '{printf "|%-15s | %-10d|\n", $NF, $3}' /etc/passwd
解析:
| : 以 | 为分隔符
|%-15s| : 以 | 为分隔符, 字符串占用15个字符位,靠左对齐
|%-10d| : 以 | 为分隔符,数字占用10个字符位,靠左对齐
\n : 换行符
$NF : 最后一列
awk数组是关联数组,下标可以是数字/字母/字符等,而shell普通数组的下标必须是数字,shell的关联数组的下标也可以是任意字符
#遍历数组: for(变量 in 数组名){ 动作指令序列 },输出是无序的。如for(i in a),i是索引,a[i]是值
|]# awk '{a[$1]++} END{for(i in a) print i,a[i]}' access.log|sort -k2 -nr
172.168.1.3 4
172.168.1.1 3
172.168.1.2 2
#数组成员关系判断
]# awk 'BEGIN{ a[11]=1;a[22]=2; if("22" in a){print "yes"} else {print no} }'
yes
#调用wc -l命令统计使用bash 的用户个数,等同于grep -c "bash$" /etc/passwd
]# awk -F: '/bash$/{print|"wc -l"}' /etc/passwd
8
#调用system("") 来调用命令
awk 'BEGIN{system("ls")}'
]# awk -F: '/^root/{system("echo `date +%F` "$1)}' /etc/passwd
2024-06-18 root
能让awk立刻读取下一行数据(读取下一条记录并复制给$0,并重新设置NF、NR和FNR)
#打印偶数行
]# echo -e "1\n2\n3\n4"|awk '{getline;print $0}'
2
4
停止处理当前的输入记录,立刻读取下一条记录并返回awk程序的第一个模式匹配重新处理数据。 有点类似于循环语句中的continue,不会执行当次循环的后续语句
]# echo -e "1\n2\n3\n4"|awk '$1=="2"{next}{print $1}'
1
3
4
]# echo -e "1\n2\n3\n4"|awk '{if($1=="2")next;print $1}'
1
3
4
cos(expr)、sin(expr)、sqrt(expr)
]# awk 'BEGIN{print int(6.8)}'
6
awk 'BEGIN{print rand()}'
awk 'BEGIN{for(i=1;i<=5;i++) print int(100*rand())}' #生成5个100以内的随机数
awk 'BEGIN{test="hello"; print length(test)}' #打印字符串长度
awk 'BEGIN{t[0]="hi";t[1]="the"; print length(t)}' #返回数组元素个数
awk '{print length()}' /etc/shells #返回文件每行的字符长度
]# awk 'BEGIN{test="hello";print index(test,"l")}'
3
awk 'BEGIN{print match("How much","[a-z]")}' #小写字母在第2个位置开始出现
2
]# awk 'BEGIN{print tolower("HELLo")}'
hello
]# awk 'BEGIN{print toupper("HELLo")}'
HELLO
将字符串按特定的分隔符切片后存储在数组中,如果没指定分隔符,则使用IFS定义的。 数组下标从1开始
awk 'BEGIN{split("hello world",test); print test[1],test[2]}'
awk 'BEGIN{split("hello:world",test,":"); print test[1],test[2]}' #指定冒号(:)为分隔符
将字符串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
与gsub类似,但仅替换第一个匹配的字符串,而不是替换全部
对字符串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(3,5)}'
5
cat example.txt | awk 'NR%2==1' #打印奇数行
cat example.txt | awk 'NR%2==0' #打印偶数行
#制表符分隔输出多字段
awk -F'[:#]' '{print $1,$2,$3,$4,$5,$6,$7}' OFS='\t' helloworld.sh
#制表符分隔输出多字段,效果同上
awk -F'[:#]' '{NF=NF;print $0}' OFS='\t' helloworld.sh
//计算/root目录下,普通文件的大小,使用KB作为单位
]# ls -l /root|awk 'BEGIN{sum=0} NR>1{sum+=$5} END{print "total size is:",sum/1024,"KB"}'
total size is: 10680.3 KB
]# ls -l /root/|awk 'BEGIN{sum=0} !/^d/{sum+=$5} END{print "total size is:",int(sum/1024),"KB"}'
total size is: 10672 KB
#统计netstat -anp 状态为LISTEN和CONNECT的连接数量分别是多少
netstat -anp|awk '$6~/LISTEN|CONNECTED/{sum[$6]++} END{for (i in sum) printf "%-10s %-6s %-3s \n", i," ",sum[i]}'
#统计/etc/passwd中不同登录shell的数量
]# awk -F: '{sum[$NF]++}END{for(i in sum)print i,sum[i]}' /etc/passwd|sort -k2 -nr
/sbin/nologin 24
/bin/bash 8
/sbin/shutdown 1
/sbin/halt 1
/bin/sync 1
#统计文件中空行数
]# awk 'BEGIN{X=0} /^$/{ X+=1 } END{print "I find",X,"blank lines."}' test
I find 4 blank lines.
#统计访问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
---------------------------------------------------------------------------------------------------------
#输出成绩表
]# cat test0
Marry 2143 78 84 77
Jack 2321 66 78 45
Tom 2122 48 77 71
Mike 2537 87 97 95
Bob 2415 40 57 62
]# awk 'BEGIN{math=0;eng=0;com=0;printf "Lineno. Name No. Math English Computer Total\n";printf "------------------------------------------------------------\n"}{math+=$3; eng+=$4; com+=$5;printf "%-8s %-7s %-7s %-7s %-9s %-10s %-7s \n",NR,$1,$2,$3,$4,$5,$3+$4+$5} END{printf "------------------------------------------------------------\n";printf "%-24s %-7s %-9s %-20s \n","Total:",math,eng,com;printf "%-24s %-7s %-9s %-20s \n","Avg:",math/NR,eng/NR,com/NR}' test0
Lineno. Name No. Math English Computer Total
------------------------------------------------------------
1 Marry 2143 78 84 77 239
2 Jack 2321 66 78 45 189
3 Tom 2122 48 77 71 196
4 Mike 2537 87 97 95 279
5 Bob 2415 40 57 62 159
------------------------------------------------------------
Total: 319 393 350
Avg: 63.8 78.6 70