文本三剑客之awk进阶篇

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了文本三剑客之awk进阶篇相关的知识,希望对你有一定的参考价值。

此篇主要讲解awk控制语句,捎带说明getline和NR_FNR命令的使用。其中会用到awk基础知识,若有疑惑,可点击文本三剑客之awk基础篇 进行翻阅。

示例文件

以下为通篇会用到的几个示例文件:

[[email protected] awk_file]# cat file.txt
name yu shu wai
rick 80 86 90
long 68 89 78
jack 66 60 82
[[email protected] awk_file]# cat file.txt1
name yu shu wai 
rick 80 86 90 
long 68 89 78 
jing 76 80 66 
jack 66 60 82 
[[email protected] awk_file]# cat rep.txt 
hello 2018 01
nihao 2018 02
hello 2017 02
nihao 2017 01
hello 2018 01 aa
hello 2018 01
nihao 2017 02
nihao 2017 01

加{ }和不加的区别

[[email protected] awk_file]# awk ‘{/[0-9]+/}‘ file.txt    #不输出内容     
[[email protected] awk_file]# awk ‘/[0-9]+/‘ file.txt  
输出内容,相当于:
[[email protected] awk_file]# awk ‘{if(/[0-9]+/) print }‘ file.txt  
rick 80 86 90
long 68 89 78
jack 66 60 82

去重

以下两种去重方法,若存在重复的行,会取最上面的一行记录(以aa进行参考):

[[email protected] awk_file]# awk ‘!a[$0]++‘ rep.txt 
hello 2018 01
nihao 2018 02
hello 2017 02
nihao 2017 01
hello 2018 01 aa
nihao 2017 02
[[email protected] awk_file]# awk ‘++a[$0]==1‘ rep.txt  
hello 2018 01
nihao 2018 02
hello 2017 02
nihao 2017 01
hello 2018 01 aa
nihao 2017 02

以$1和$2为基准进行去重:

[[email protected] awk_file]# awk ‘++a[$1 $2]==1‘ rep.txt 
hello 2018 01
nihao 2018 02
hello 2017 02
nihao 2017 01

说明:
1)表达式!a[$0]++,当$0第一次出现时,a[$0]的值为0,此时先不进行++操作,那!a[$0]就为1,即进行输出;
2)当$0第二次出现时,a[$0]会先进行++操作,即a[$0]++为a[$0]=a[$0]+1=1,所以!a[$0]为0,不进行输出,依次类推;
3)只要a[$0]的值非0,那么!a[$0]结果就为假,不进行输出;
4)而++a[$0]是先进行自加再引用,所以当++a[$0]==1时,即进行输出第一次出现的行。

getline命令

getline从标准输入、管道或者当前正在处理的文件之外的其他输入文件获得输入。它负责从输入获得下一行的内容,并给NF,NR和FNR等内建变量赋值。如果得到一条记录,getline函数返回1,如果到达文件的末尾就返回0,如果出现错误,例如打开文件失败,就返回-1。

getline语法:getline var  #将get到的行赋值给变量var 

awk getline从整体上来说,用法说明:
? 当其左右重定向符 | 或 < 时:getline作用于当前文件,读入当前文件的第一行给其后跟的变量var或$0(无变量),应该注意到,由于awk在处理getline之前已经读入了一行,所以getline得到的返回结果是隔行的。
? 当其左右重定向符 | 或 < 时:getline则作用于定向输入文件,由于该文件是刚打开,并没有被awk读入一行,只是getline读入,那么getline返回的是该文件的第一行,而不是隔行。

1)以下为getline的常见用法:
[[email protected] awk_file]# awk ‘BEGIN{"ls -l"|getline a;print a}‘
[[email protected] awk_file]# awk ‘BEGIN{"ls -l"|getline ;print }‘
total 44
[[email protected] awk_file]# awk ‘BEGIN{getline a<"file.txt" ;print a}‘
[[email protected] awk_file]# awk ‘BEGIN{getline <"file.txt" ;print }‘
name yu shu wai
PS:以上命令,因为getline只得到第一行,所以仅会打印第一行
2)打印偶数行:
[[email protected] awk_file]# seq 10 |awk ‘{getline;print}‘
[[email protected] awk_file]# seq 10 |awk ‘{getline a;print a}‘
2
4
6
8
10
PS:awk先读入第一行,接着处理getline,并通过getline将下一行赋值给a。
3)结合while使用:
[[email protected] awk_file]# seq 10 |awk ‘{while(getline)print}‘     
2
3
4
5
[[email protected] awk_file]# seq 10 |awk ‘BEGIN{while(getline)print}‘  
1
2
3
4
5
。。。。。。
[[email protected] awk_file]# awk ‘BEGIN{while(getline a<"file.txt")print a}‘
name yu shu wai
rick 80 86 90
long 68 89 78
jack 66 60 82
[[email protected] awk_file]# awk ‘BEGIN{while(getline a<"file.txt")b++;print b}‘ 
4
[[email protected] awk_file]# awk ‘BEGIN{while(getline <"file.txt")b++;print b}‘ 
4
[[email protected] awk_file]# awk ‘BEGIN{while("cat file.txt"|getline)b++;print b}‘         
4
PS:因为while是循环语句,所以getline会得到所有行。

awk函数

内置函数

数值处理:

rand( ): 随机产生一个0到1之间的小数(保留小数点后6位的值);

字符串处理:

length([s]):返回指定字符串s的长度(字符个数),如果没有提供s,则返回$0的长度;
split(string, array [, r [, seps] ]):以r为分隔符进行切割字符串string,并将切割后的结果保存至数组array中,数组下标从0开始排序。若省略r,则默认由FS代替,即空白字符;
gsub(r, s [, t]):由正则表达式r所表示的模式,来匹配字符串t中的字符,将匹配到的所有字符替换为s。如果没有提供t,则默认为$0。相当于sed ‘s///g’;
sub(r, s [, t]):和gsub相似,将第一次由r匹配到的t中的字符替换为s。相当于sed ‘s///’;
substr(s, i [, n]):取字符串s中的从i开始到n个子串, 若省略n,则取剩余的所有s中的字符;
tolower(s):将s中的大写字母转换为小写;
toupper(s):将s中的小写字母转换为大写;

1)统计外部客户端访问服务器的IP数量:

[[email protected] ~]# netstat -tan |awk ‘/^tcp\>/{split($5,client,":");ip[client[1]]++}END{for(i in ip)print i,ip[i]}‘
192.168.3.29 1
0.0.0.0 8

2)将第二列的数字替换为00:

[[email protected] awk_file]# awk ‘gsub(/[0-9]+/,"00",$2)‘ file.txt 
rick 00 86 90
long 00 89 78
jack 00 60 82

自定义函数

使用function进行自定义函数,格式如下:

function fname([var]){ statements }

if-else判断语句

语法格式:{if (condition) {statements}  [else {statements}] }    #[ ]中的语句表示可选

1)打印出普通用户和系统用户

[[email protected]node1 awk_file]# awk -F: ‘{if($3>=1000)print $1,":Common User";else print $1,":Sys User"}‘ /etc/passwd
。。。。。。
mysql :Sys User
saslauth :Sys User
dovecot :Sys User
dovenull :Sys User
tony :Common User
rick :Common User
。。。。。。。

2)统计普通用户有多少个

[[email protected] awk_file]# awk -F: ‘{if($3>=1000)user++}END{print user}‘ /etc/passwd
6

for循环语句

语法格式:{for (expr1;expr2;expr3) statements}
数组用法:{for (var in array) statement}

1)将file.txt中的内容,以行为单位,打印成一列:

[[email protected] awk_file]# awk ‘{for(i=1;i<=NF;i++)print $i}‘ file.txt
name
yu
shu
wai
rick
80
86
。。。。。。
PS :awk命令本身就是以循环的方式进行处理,所以语句中的for循环,可以看成嵌套的子循环 ,从而将每行的每个字段,按单列进行打印。

2)再将其恢复成原内容:

[[email protected] awk_file]# awk ‘{for(i=1;i<=NF;i++)printf $i" "}‘ file.txt                        
name yu shu wai rick 80 86 90 long 68 89 78 jack 66 60 82 [[email protected] awk_file]# 
[[email protected] awk_file]# awk ‘{for(i=1;i<=NF;i++)printf $i" ";print var}‘ file.txt              
或
awk ‘{for(i=1;i<=NF;i++){printf $i" "}print var}‘ file.txt
name yu shu wai 
rick 80 86 90 
long 68 89 78 
jack 66 60 82 
PS:for(i=1;i<=NF;i++){printf $i" "}为每行的一个字段间循环,处理完毕后,再执行print var;
print var表示打印一个空值,即换行。

3)打印奇数列:

[[email protected] awk_file]# awk ‘{for (i=1;i<=NF;i+=2)printf $i" ";print a}‘ file.txt
name shu 
rick 86 
long 89 
jack 60 

4)打印偶数列:

[[email protected] awk_file]# awk ‘{for (i=2;i<=NF;i+=2)printf $i" ";print a}‘ file.txt
yu wai 
80 90 
68 78 
66 82

遍历数组

一维数组

1)统计系统中使用相同bash shell的用户的数量:

~]# awk -F: ‘{shell[$NF]++}END{for(A in shell) print A,shell[A]}‘ /etc/passwd
/bin/sync 1
/bin/bash 5
/sbin/nologin 26
/bin/tcsh 1
/sbin/halt 1
/sbin/shutdown 1

分析

shell[$NF]=shell[/bin/bash]即/bin/bash为数组shell的下标;
在awk中变量第一次引用的初始值为0,即shell[$NF]=0,所以shell[$NF]++的元素值为1;这样shell[$NF]++就可以计算出,相同bash的数量。
shell[$NF]++也可以写成++shell[$NF];
A in shell中的A表示数组shell的每一个下标的名称,如A=/bin/bash,所以print A即打印出下标的名称;
shell[A]为shell数组下标为[A]的元素值,即用户默认相同shell的数量。
netstat –tan:当前正在监听或已建立连接的TCP的连接状态。

2)统计netstat中每一种tcp连接状态出现的次数:

~]# netstat -tan |awk ‘NR>2{state[$NF]++}END{for(A in state) {print A,state[A]}}‘ 
或
~]# netstat -tan |awk ‘/^tcp/{state[$NF]++}END{for(A in state) {print A,state[A]}}‘
LISTEN 15
ESTABLISHED 1

3)统计外部IP分别访问web的次数:

~]# awk ‘{++count[$1]}END{for (ip in count) {printf "%-20s,%5d\n",ip,count[ip]}}‘ /var/log/httpd/access_log   
192.168.115.148     ,   40
::1                 ,   25
192.168.115.1       ,  755
192.168.115.145     ,  142

二维数组

使用二维数组实现行列互换,最终要实现的结果:

name rick long jack tony 
yu 80 68 66 76 
shu 86 89 60 90 
wai 90 78 82 66

思路分析

1)原file.txt内容所对应的数组排列(五行四列):
a[1,1]=name a[1,2]=yu a[1,3]=shu a[1,4]=wai
a[2,1]=rick a[2,2]=80 a[2,3]=86 a[2,4]=90
a[3,1]=long a[3,2]=68 a[3,3]=89 a[3,4]=78
a[4,1]=jack a[4,2]=66 a[4,3]=60 a[4,4]=82
a[5,1]=tony a[5,2]=76 a[5,3]=90 a[5,4]=66
命令实现:for(i=1;i<=NF;i++)a[NR,i]=$i
2)最终结果所对应的数组排列(四行五列):
a[1,1]=name a[2,1]=rick a[3,1]=long a[4,1]=jack a[5,1]=tony
a[1,2]=yu a[2,2]=80 a[3,2]=68 a[4,2]=66 a[5,2]=76
a[1,3]=shu a[2,3]=86 a[3,3]=89 a[4,3]=60 a[5,3]=90
a[1,4]=wai a[2,4]=90 a[3,4]=78 a[4,4]=82 a[5,4]=66

命令实现说明

for(i=1;i<=NF;i++)  # i取值:1、2、3、4
for(j=1;j<=NR;j++)  # j取值:1、2、3、4、5
当i 取值1~4时,其每个值可获得5次排列组合,使用a[j,i] 可实现想要的数组(四行五列),如下:
i=1 时,a[j,i]= a[1,1] a[2,1] a[3,1] a[4,1] a[5,1]
i=2 时,a[j,i]= a[1,2] a[2,2] a[3,2] a[4,2] a[5,2]
i=3 时,a[j,i]= a[1,3] a[2,3] a[3,3] a[4,3] a[5,3]
i=4 时,a[j,i]= a[1,4] a[2,4] a[3,4] a[4,4] a[5,4]

最终命令实现

[[email protected] awk_file]# 
awk ‘{for(i=1;i<=NF;i++)a[NR,i]=$i}END{for(i=1;i<=NF;i++){for(j=1;j<=NR;j++){printf a[j,i]" "}print aa}}‘ file.txt
name rick long jack tony 
yu 80 68 66 76 
shu 86 89 60 90 
wai 90 78 82 66
PS:print aa(aa为空值)目的是每输出一行内容,执行换行操作,也可以写成printf “\n”。

此处参考:https://www.cnblogs.com/wangyuebo/p/5904565.html

while循环语句

语法格式:{while (condition) {statements } }   #条件为true,开始循环;条件为false,退出循环

1)打印出每行超过4个字符的字段:

awk -F: ‘{i=1;while (i<=NF){if (length($i)>=4) {print $i};i++}}‘ /etc/passwd
awk -F: ‘{for (i=1;i<=NF;i++) {if(length($i)>4) print $i}}‘ /etc/passwd

2)将file.txt中的内容,以行为单位,打印成一列:

[[email protected] awk_file]# awk ‘{i=1;while (i<=NF){print $i;i++}}‘ file.txt
[[email protected] awk_file]# awk ‘{for(i=1;i<=NF;i++)print $i}‘ file.txt

do-while循环语句

语法:do{ statements }while(condition)   #先执行一遍statements再进行while条件判断

将行打印为一列:

[[email protected] awk_file]# awk ‘{i=1;do{print $i;i++}while(i<=NF)}‘ file.txt

next和exit

next:提前结束对本行的处理,而直接进入下一行处理;
exit:直接退出后面所有行的处理;

1)打印ID为奇数的用户:

[[email protected] awk_file]# awk -F: ‘{if($3%2==0)next;print $1,$3}‘ /etc/passwd
[[email protected] awk_file]# awk -F: ‘{if($3%2!=0)print $1,$3}‘ /etc/passwd

2)next和exit的区别,及语句中若存在条件和多个{ action } 时,是如何执行的:

[[email protected] awk_file]# cat file
5
4
3
2
1
[[email protected] awk_file]# awk ‘$1==3{printf " ## "$0}{printf " @@ "$0}{print " aa"}‘ file       
 @@ 5 aa
 @@ 4 aa
 ## 3 @@ 3 aa
 @@ 2 aa
 @@ 1 aa
[[email protected] awk_file]# awk ‘$1==3{printf " ## "$0;next}{printf " @@ "$0}{print " aa"}‘ file 
 @@ 5 aa
 @@ 4 aa
 ## 3 @@ 2 aa
 @@ 1 aa
[[email protected] awk_file]# awk ‘$1==3{printf " ## "$0;exit}{printf " @@ "$0}{print " aa"}‘ file 
 @@ 5 aa
 @@ 4 aa
 ## 3[[email protected] awk_file]#

NR和FNR练习

1)打印file.txt的第三列和file.txt1的第一列

[[email protected] awk_file]# awk ‘1‘ file.txt file.txt1
name yu shu wai  hhh
rick 80 86 90  hhh
long 68 89 78  hhh
jack 66 60 82  hhh
name yu shu wai 
rick 80 86 90 
long 68 89 78 
jing 76 80 66 
jack 66 60 82 [[email protected] awk_file]# awk ‘{print FNR,NR}‘ file.txt file.txt1
1 1
2 2
3 3
4 4
1 5
2 6
3 7
4 8
5 9
最终命令实现:
[[email protected] awk_file]# awk ‘NR==FNR{a[NR]=$1}NR!=FNR{print $3,a[FNR]}‘ file.txt file.txt1
shu name
86 rick
89 long
80 jack
60

说明:

符合NR==FNR条件的为紧接着awk命令的文件file.txt,因为NR和FNR显示第一个文件的行数是一样的;
符合NR!=FNR条件的为第二个文件file.txt1,因为此时FNR会从1开始打印行号;
a[NR]=$1也可以写成a[FNR]=$1;
因为NR==FNR时,会把file.txt的第一列复制给a[NR]=$1(NR取值范围1-4);
在NR!=FNR的print file.txt第一列时,需用a[FNR]实现,因此时FNR的取值范围是1-5,所以会打印file.txt的第一列(FNR取值5时是空值);

2)将text1和text内容合并,并以text中的IP排序:
习题链接:http://bbs.chinaunix.net/thread-4245971-1-3.html

[[email protected] awk_file]# cat text1
173.199.5.16    15%   packet loss
202.97.34.42    10%   packet loss
202.97.50.90    10%   packet loss
202.97.82.33    0%    packet loss
4.53.116.110    26%   packet loss
4.53.210.209    22%   packet loss
4.69.149.18     6%    packet loss
4.69.149.210    26%   packet loss
[[email protected] awk_file]# cat text
202.97.82.33 中国 电信骨干网
202.97.34.42 中国 电信骨干网
202.97.50.90 中国 电信骨干网
4.53.210.209 美国
4.69.149.18 美国
4.69.149.210 美国
4.53.116.110 美国
173.199.5.16 美国
[[email protected] awk_file]# awk ‘NR==FNR{a[$1]=$0}NR!=FNR{print a[$1]}‘ text1 text
202.97.82.33    0%    packet loss
202.97.34.42    10%   packet loss
202.97.50.90    10%   packet loss
4.53.210.209    22%   packet loss
4.69.149.18     6%    packet loss
4.69.149.210    26%   packet loss
4.53.116.110    26%   packet loss
173.199.5.16    15%   packet loss

以下为命令实现的几种写法:

[[email protected] awk_file]# awk ‘NR==FNR{a[$1]=$0}NR!=FNR{$1=a[$1];print $0}‘ text1 text      
或
[[email protected] awk_file]# awk ‘NR==FNR{a[$1]=$0;next}NR!=FNR{$1=a[$1];print $0}‘ text1 text
或
[[email protected] awk_file]# awk ‘NR==FNR{a[$1]=$0;next}NR!=FNR{$1=a[$1]}1‘ text1 text
或
[[email protected] awk_file]# awk ‘NR==FNR{a[$1]=$0;next}{$1=a[$1]}1‘ text1 text
202.97.82.33    0%    packet loss 中国 电信骨干网
202.97.34.42    10%   packet loss 中国 电信骨干网
202.97.50.90    10%   packet loss 中国 电信骨干网
4.53.210.209    22%   packet loss 美国
4.69.149.18     6%    packet loss 美国
4.69.149.210    26%   packet loss 美国
4.53.116.110    26%   packet loss 美国
173.199.5.16    15%   packet loss 美国
注:awk在处理多文件时,以谁为准,最后处理谁,即写在最后。
[[email protected] awk_file]# awk ‘NR==FNR{a[$1]=$0;next}{k=$1;$1="";print a[k]$0}‘ text1 text
PS:k=$1传递给了a[k],$1=""传递给了$0。

以上是关于文本三剑客之awk进阶篇的主要内容,如果未能解决你的问题,请参考以下文章

awk入门及进阶

文本处理三剑客之awk

文本三剑客之awk

文本处理三剑客之AWK

文本三剑客之awk

文本处理三剑客之gawk