上一篇:七、shell脚本语言文本处理awk(二)
目录
11、awk的I/O 语句
11.1、getline
11.2、getline var
11.3、command | getline [var]
11.4、next
11.5、system()
11.6、打印结果写到文件
11.7、管道连接 shell 命令
12、awk的printf 语句
12.1、 printf 语句示例
13、自定义函数
14、需求案例
14.1、NGINX日志文件分析
14.2、两个文件对比
14.3、将 a 文件相同 IP 的服务名合并
14.4、将第一列合并到一行
14.5、字符串拆分,统计出现的次数
14.6、统计平均成绩
14.7、费用统计
14.8、获取数字字段最大值
14.9、去除第一行和最后一行
总结:
语句 | 作用描述 |
getline | 读取下一个输入记录设置给$0 |
getline var | 读取下一个输入记录并赋值给变量 var |
command | getline [var] | 运行 Shell 命令管道输出到$0 或 var |
next | 停止当前处理的输入记录后面动作 |
打印当前记录 | |
printf fmt, expr-list | 格式化输出 |
printf fmt, expr-list >file | 格式输出和写到文件 |
system(cmd-line) | 执行命令和返回状态 |
print ... >> file | 追加输出到文件 |
print ... | command | 打印输出作为命令输入 |
getline ---------------------读取下一个输入记录设置给$0
sub(r, s [, t]) -----------------对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,但只替 换第一个字符串
获取匹配的下一行
[root@~ test]# seq 5 |awk '/3/{getline;print}'
4
[root@~ test]# seq 5 |awk '/3/{print;getline;print}'
3
4
在匹配的下一行加个星号
[root@~ test]# seq 5 |awk '/3/{getline;sub(".*","&*");print}'
4*
[root@~ test]# seq 5 |awk '/3/{print;getline;sub(".*","&*")}{print}'
1
2
3
4*
5
getline var --------------读取下一个输入记录并赋值给变量 var
把 a 文件的行追加到 b 文件的行尾:
[root@~ test]# cat a
a
b
c
[root@~ test]# cat b
1 one
2 two
3 three
[root@~ test]# awk '{getline line<"a";print $0,line}' b
1 one a
2 two b
3 three c
把 a 文件的行替换 b 文件的指定字段:
[root@~ test]# awk '{getline line<"a";gsub($2,line,$2);print}' b
1 a
2 b
3 c
把 a 文件的行替换 b 文件的对应字段:
[root@~ test]# awk '{getline line<"a";gsub("two",line,$2);print}' b
1 one
2 b
3 three
command | getline [var] ----------运行 Shell 命令管道输出到$0 或 var
获取执行 shell 命令后结果的第一行:
[root@~ test]# awk 'BEGIN{"seq 5"|getline var;print var}'
1
[root@~ test]# seq 5 | awk 'BEGIN{getline var;print var}'
1
循环输出执行 shell 命令后的结果:
[root@~ test]# awk 'BEGIN{while("seq 5"|getline)print}'
1
2
3
4
5
[root@~ test]# seq 5 |awk 'BEGIN{while(getline)print}'
1
2
3
4
5
next --------------------停止当前处理的输入记录后面动作
不打印匹配行:
[root@~ test]# seq 5 |awk '{if($0==3){next}else{print}}'
1
2
4
5
删除指定行:
[root@~ test]# seq 5 |awk 'NR==1{next}{print $0}'
2
3
4
5
或
[root@~ test]# seq 5 |awk '{if($0==1){next}else{print}}'
2
3
4
5
如果前面动作成功,就遇到 next,后面的动作不再执行,跳过。
[root@~ test]# seq 5 |awk 'NR!=1{print}'
2
3
4
5
把第一行内容放到每行的前面:
[root@~ test]# cat c
hello
1 a
2 b
3 c
[root@~ test]# awk 'NR==1{s=$0;next}{print s,$0}' c
hello 1 a
hello 2 b
hello 3 c
或
[root@~ test]# awk 'NR==1{s=$0}NF!=1{print s,$0}' c
hello 1 a
hello 2 b
hello 3 c
system(cmd-line) ------------执行命令和返回状态
执行 shell 命令判断返回值:
[root@~ test]# awk 'BEGIN{if(system("grep root /etc/passwd &>/dev/null")==0)print "yes";else print "no"}'
yes
[root@~ test]# awk '{print $2 > "bv.txt"}' servers
[root@~ test]# cat bv.txt
48003/udp
48049/tcp
48128/tcp
48128/udp
48129/tcp
48129/udp
48556/tcp
48556/udp
48619/tcp
48619/udp
[root@~ test]# awk '{print $2|"grep tcp"}' servers
48049/tcp
48128/tcp
48129/tcp
48556/tcp
48619/tcp
格式化输出,默认打印字符串不换行。
格式:printf [format] arguments
Format | 作用描述 |
%s | 一个字符串 |
%d,%i | 一个小数 |
%f | 一个浮点数 |
%.ns | 输出字符串,n 是输出几个字符 |
%m.nf | 输出浮点数,m 是输出整数位数,n 是输出的小数位数 |
%x | 不带正负号的十六进制,使用 a 至 f 表示 10 到 15 |
%X | 不带正负号的十六进制,使用 A 至 F 表示 10 至 15 |
%% | 输出单个% |
%-5s | 左对齐,对参数每个字段左对齐,宽度为 5 |
%-4.2f | 左对齐,宽度为 4,保留两位小数 |
%5s | 右对齐,不加横线表示右对齐 |
将换行符换成逗号:
小括号中的 5 是最后一个数字
[root@~ test]# seq 5 |awk '{if($0!=5)printf "%s,",$0;else print $0}'
1,2,3,4,5
[root@~ test]# seq 6 |awk '{if($0!=5)printf "%s,",$0;else print $0}'
1,2,3,4,5
6,
[root@~ test]# seq 10 |awk '{if($0!=5)printf "%s,",$0;else print $0}'
1,2,3,4,5
6,7,8,9,10,
输出一个字符:
[root@~ test]# awk 'BEGIN{printf "%.1s\n","abc"}'
a
保留一个小数点后多少位:
[root@~ test]# awk 'BEGIN{printf "%.2f\n",10/3}'
3.33
格式化输出:
[root@~ test]# awk 'BEGIN{printf "user:%s\tpass:%d\n","abc",123}'
user:abc pass:123
左对齐宽度 10:
[root@~ test]# awk 'BEGIN{printf "%-10s %-10s %-10s\n","ID","Name","Passwd"}'
ID Name Passwd
右对齐宽度 10:
[root@~ test]# awk 'BEGIN{printf "%10s %10s %10s\n","ID","Name","Passwd"}'
ID Name Passwd
打印表格:
[root@~ test]# cat test.awk
BEGIN{
print "+--------------------+--------------------+";
printf "|%-20s|%-20s|\n","Name","Number";
print "+--------------------+--------------------+";
}
[root@~ test]# awk -f test.awk
+--------------------+--------------------+
|Name |Number |
+--------------------+--------------------+
格式化输出:
[root@~ test]# awk -F: 'BEGIN{printf "UserName\t\tShell\n-----------------------------\n"}{printf "%-20s %-20s\n",$1,$7}END{print "END...\n"}' /etc/passwd
UserName Shell
-----------------------------
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
sync /bin/sync
shutdown /sbin/shutdown
halt /sbin/halt
mail /sbin/nologin
operator /sbin/nologin
games /sbin/nologin
ftp /sbin/nologin
nobody /sbin/nologin
systemd-network /sbin/nologin
dbus /sbin/nologin
polkitd /sbin/nologin
sshd /sbin/nologin
postfix /sbin/nologin
chrony /sbin/nologin
rhlog /bin/bash
rhroot /bin/bash
mwop /bin/bash
deployop /bin/bash
centos /bin/bash
nginx /sbin/nologin
tcpdump /sbin/nologin
END...
打印十六进制:
[root@~ test]# awk 'BEGIN{printf "%x %X",123,123}'
7b 7B
格式:function name(parameter list) { statements }
[root@~ test]# awk 'function myfunc(a,b){return a+b}BEGIN{print myfunc(1,2)}'
3
日志格式:
'$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent " $http_referer" "$http_user_agent" "$http_x_forwarded_for"'
统计访问 IP 次数:
# awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log
统计访问访问大于 100 次的 IP:
# awk '{a[$1]++}END{for(v in a){if(a[v]>100)print v,a[v]}}' access.log
统计访问 IP 次数并排序取前 10:
# awk '{a[$1]++}END{for(v in a)print v,a[v] |"sort -k2 -nr |head -10"}' access.log
统计时间段访问最多的 IP:
# awk '$4>="[02/Jan/2017:00:02:00" && $4<="[02/Jan/2017:00:03:00"{a[$1]++}END{for(v in a)print v,a[v]}' access.log
统计上一分钟访问量:
# date=$(date -d '-1 minute' +%d/%d/%Y:%H:%M)
# awk -vdate=$date '$4~date{c++}END{print c}' access.log
统计访问最多的 10 个页面:
# awk '{a[$7]++}END{for(v in a)print v,a[v] |"sort -k1 -nr|head n10"}' access.log
统计每个 URL 数量和返回内容总大小:
# awk '{a[$7]++;size[$7]+=$10}END{for(v in a)print a[v],v,size[v]}' access.log
统计每个 IP 访问状态码数量:
# awk '{a[$1" "$9]++}END{for(v in a)print v,a[v]}' access.log
统计访问 IP 是 404 状态次数:
# awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print v,a[v]}' access.log
对比两个文件相同之处
准备两个文件
[root@~ test]# seq 1 5 > aa
[root@~ test]# seq 3 7 > bb
对比
[root@~ test]# awk 'FNR==NR{a[$0];next}{if($0 in a)print $0}' aa bb
3
4
5
[root@~ test]# awk 'FNR==NR{a[$0];next}{if($0 in a)print FILENAME,$0}' aa bb
bb 3
bb 4
bb 5
[root@~ test]# awk 'FNR==NR{a[$0]}NR>FNR{if($0 in a)print $0}' aa bb
3
4
5
[root@~ test]# awk 'FNR==NR{a[$0]=1;next}(a[$0]==1)' aa bb
3
4
5
[root@~ test]# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}' aa bb
3
4
5
[root@~ test]# awk 'FILENAME=="aa"{a[$0]}FILENAME=="bb"{if($0 in a)print $0}' aa bb
3
4
5
[root@~ test]# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]==1' aa bb
3
4
5
找出 b 文件在 a 文件不同记录
[root@~ test]# awk 'FNR==NR{a[$0];next}!($0 in a)' aa bb
6
7
[root@~ test]# awk 'FNR==NR{a[$0]=1;next}(a[$0]!=1)' aa bb
6
7
[root@~ test]# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}' aa bb
6
7
[root@~ test]# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]!=1' aa bb
6
7
[root@~ test]# awk 'FILENAME=="aa"{a[$0]=1}FILENAME=="bb" && a[$0]!=1' aa bb
6
7
合并两个文件
[root@~ test]# cat aaa
zhangsan 20
lisi 23
wangwu 29
[root@~ test]# cat bbb
zhangsan man
lisi woman
wangwu man
[root@~ test]# awk 'FNR==NR{a[$1]=$0;next}{print a[$1],$2}' aaa bbb
zhangsan 20 man
lisi 23 woman
wangwu 29 man
[root@~ test]# awk 'FNR==NR{a[$1]=$0}NR>FNR{print a[$1],$2}' aaa bbb
zhangsan 20 man
lisi 23 woman
wangwu 29 man
FS ----------------输入字段分隔符,默认是空格或制表符
OFS -----------------输出字段分隔符,默认是空格
[root@~ test]# cat a.txt
192.168.1.1: httpd
192.168.1.1: tomcat
192.168.1.2: httpd
192.168.1.2: postfix
192.168.1.3: mysqld
192.168.1.4: httpd
[root@~ test]# awk 'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1] $2}END{for(v in a)print v,a[v]}' a.txt
192.168.1.1: httpd tomcat
192.168.1.2: httpd postfix
192.168.1.3: mysqld
192.168.1.4: httpd
解析分析:
数组 a 存储是$1=a[$1] $2,第一个 a[$1]是以第一个字段为下标,值是 a[$1] $2,也就是 $1=a[$1] $2,值的 a[$1]是用第一个字段为下标获取对应的值,但第一次数组 a 还没有元素,那么 a[$1]是空值,此时数组存储是 192.168.1.1=httpd,再遇到 192.168.1.1 时,a[$1]通过第一字段 下标获得上次数组的 httpd,把当前处理的行第二个字段放到上一次同下标的值后面,作为下标 192.168.1.1 的新值。此时数组存储是 192.168.1.1=httpd tomcat。每次遇到相同的下标(第一个 字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。
NF ----------------------统计当前记录中字段个数
[root@~ test]# cat files
1 2 3
4 5 6
7 8 9
[root@~ test]# awk '{for(i=1;i<=NF;i++)a[i]=a[i]$i" "}END{for(v in a)print a[v]}' files
1 4 7
2 5 8
3 6 9
解析分析:
for 循环是遍历每行的字段,NF 等于 3,循环 3 次。
读取第一行时:
第一个字段:a[1]=a[1]1" " 值 a[1]还未定义数组,下标也获取不到对应的值,所以为空,因此 a[1]=1 。
读第二个字段:a[2]=a[2]2" " 值 a[2]数组 a 已经定义,但没有 2 这个下标,也获取不到对应的 值,为空,因此 a[2]=2 。
读第三个字段:a[3]=a[3]3" " 值 a[3]数组 a 已经定义,但没有 3 这个下标,也获取不到对应的 值,为空,因此 a[3]=3。
读取第二行时:
第一个字段:a[1]=a[1]4" " 数组 a 的 1 为下标对应的值为1,因此 a[1]=1 4
第二个字段:a[2]=a[2]5" " 数组 a 的 2 为下标对应的值为2, 因此a[2]=2 5
第三个字段:a[3]=a[3]6" " 数组 a 的 3 为下标对应的值为3, 因此a[2]=3 6
读取第三行时:第一个字段:a[1]=a[1]7" " 数组 a 的 1 为下标对应的值为1,因此a[1]=1 4 7
第二个字段:a[2]=a[2]8" " 数组 a 的 2 为下标对应的值为2,因此a[1]=2 5 8
第三发字段:a[3]=a[3]9" " 数组 a 的 3 为下标对应的值为3,因此a[3]=3 6 9
最后 for 循环输出所有下标值。
a[1]=1 4 7
a[2]=2 5 8
a[3]=3 6 9
split(s, a [, r [, seps] ]) -----根据分隔符 seps 将 s 分成数组 a
字符串拆分:
[root@~ test]# echo "hello world" |awk -F '' '{print $1}'
h
[root@~ test]# echo "hello" |awk -F '' '{for(i=1;i<=NF;i++)print $i}'
h
e
l
l
o
或
[root@~ test]# echo "hello" |awk '{split($0,a,"''");for(v in a)print a[v]}'
l
o
h
e
l
统计字符串中每个字母出现的次数:
[root@~ test]# echo "a.b.c,c.d.e" |awk -F '[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print v,a[v]}'
a 1
b 1
c 2
d 1
e 1
[root@~ test]# cat f
job 80
dave 84
tom 75
dave 73
job 72
tom 83
dave 88
[root@~ test]# awk '{a[$1]+=$2;b[$1]++}END{for(i in a)print i,a[i]/b[i]}' f
dave 81.6667
tom 79
job 76
解析分析:
a[$1]+=$2,可以理解为a[$1]=a[$1]+$2,第一次的a[$1]是找不到数据的,所以是为空,$1对应的是job的,所以第一次的a[job]=0+80,遇到相同的$1(就是job的)话,就会拿上次a[job]对应的值,那么在此遇见job的$1,就是a[job]=80+72
其他的dave和tom也是一样的逻辑,b[$1]++统计了$1个数
[root@~ test]# cat ff
zhangsan 8000 1
zhangsan 5000 1
lisi 1000 1
lisi 2000 1
wangwu 1500 1
zhaoliu 6000 1
zhaoliu 2000 1
zhaoliu 3000 1
[root@~ test]# awk '{name[$1]++;cost[$1]+=$2;number[$1]+=$3}END{for(v in name)print v,cost[v],number[v]}' ff
zhaoliu 11000 3
zhangsan 13000 2
wangwu 1500 1
lisi 3000 2
解析分析:
name[$1]++是统计相同字段出现的次数
cost[$1]+=$2可以理解为cost[$1]=cost[$1]+$2,首次的cost[$1]是为空或者是0的,这个数组的处理过程就是cost[zhangsan]=0+8000+5000的运算方式
number[$1]+=$3可以理解为number[$1]=number[$1]+$3首次的number[$1]是为空或者是0的,这数组的处理过程就是0+1+1+1的计算方式的
[root@~ test]# cat fff
a b 1
c d 2
e f 3
g h 3
i j 2
获取第三字段最大值:
[root@~ test]# awk 'BEGIN{max=0}{if($3>max)max=$3}END{print max}' fff
3
打印第三字段最大行:
[root@~ test]# awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)print v,a[v],max}' fff
g h 3 3 3
e f 3 3 3
a b 1 1 3
c d 2 2 3
i j 2 2 3
[root@~ test]# awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)if(a[v]==max)print v}'
fff
g h 3
e f 3
读取第一行,NR=1,不执行 print s,s=1
读取第二行,NR=2,不执行 print s,s=2 (大于为真)
读取第三行,NR=3,执行 print s,此时 s 是上一次 p 赋值内容 2,s=3 最后一行,执行 print s,打印倒数第二行,s=最后一行
[root@~ test]# seq 5 |awk 'NR>2{print s}{s=$0}'
2
3
4
获取 Nginx 负载均衡配置端 IP 和端口
[root@~ test]# cat nginx.conf
upstream example-servers1 {
server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;
}
upstream example-servers2 {
server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;
server 127.0.0.1:82 backup;
}
[root@~ test]# awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' nginx.conf
127.0.0.1:80
或
[root@~ test]# awk '/example-servers1/,/}/{if(i>1)print s;s=$2;i++}' nginx.conf
127.0.0.1:80
或
[root@~ test]# awk '/example-servers1/,/}/{if(i>1){print s}{s=$2;i++}}' nginx.conf
127.0.0.1:80
读取第一行,i 初始值为 0,0>1 为假,不执行 print s,x=example-servers1,i=1
读取第二行,i=1,1>1 为假,不执行 print s,s=127.0.0.1:80,i=2
读取第三行,i=2,2>1 为真,执行 print s,此时 s 是上一次 s 赋值内容 127.0.0.1:80,i=3 最后一行,执行 print s,打印倒数第二行,s=最后一行。 这种方式与上面一样,只是用 i++作为计数器。
[root@~ test]# seq 5 |awk '/3/{print s}{s=$0}'
2
shell的awk的方式去处理文本的信息,是很常使用,这个awk工具也适用于很多场景,如上就介绍了awk的使用,希望可以帮助到大家,感谢大家的支持,请点赞加关注!!!