第七章 Shell文本处理三剑客之awk
awk是一个处理文本的编程语言工具,能用简短的程序处理标准输入或文件、数据排序、计算以及生成报表等等。
在Linux系统下默认awk是gawk,它是awk的GNU版本。可以通过命令查看应用的版本:ls -l /bin/awk
基本的命令语法:awk option 'pattern {action}' file
其中pattern表示AWK在数据中查找的内容,而action是在找到匹配内容时所执行的一系列命令。花括号用于根据特定的模式对一系列指令进行分组。
awk处理的工作方式与数据库类似,支持对记录和字段处理,这也是grep和sed不能实现的。
在awk中,缺省的情况下将文本文件中的一行视为一个记录,逐行放到内存中处理,而将一行中的某一部分作为记录中的一个字段。用1,2,3...数字的方式顺序的表示行(记录)中的不同字段。用$后跟数字,引用对应的字段,以逗号分隔,0表示整个行。
8.3.1 选项选项
描述
-f program-file
从文件中读取awk程序源文件
-F fs
指定fs为输入字段分隔符
-v var=value
变量赋值
--posix
兼容POSIX正则表达式
--dump-variables=[file]
把awk命令时的全局变量写入文件,
默认文件是awkvars.out
--profile=[file]
格式化awk语句到文件,默认是awkprof.out
8.3.2 模式常用模式有:
Pattern
Description
BEGIN{ }
给程序赋予初始状态,先执行的工作
END{ }
程序结束之后执行的一些扫尾工作
/regular expression/
为每个输入记录匹配正则表达式
pattern && pattern
逻辑and,满足两个模式
pattern || pattern
逻辑or,满足其中一个模式
! pattern
逻辑not,不满足模式
pattern1, pattern2
范围模式,匹配所有模式1的记录,直到匹配到模式2
而动作呢,就是下面所讲的print、流程控制、I/O语句等。
示例:
1)从文件读取awk程序处理文件
#vitest.awk{print$2}#tail-n3/etc/services|awk-ftest.awk48049/tcp48128/tcp49000/tcp
2)指定分隔符,打印指定字段
打印第二字段,默认以空格分隔:#tail-n3/etc/services|awk'{print$2}'48049/tcp48128/tcp48128/udp指定冒号为分隔符打印第一字段:#awk-F':''{print$1}'/etc/passwdrootbindaemonadmlpsync......
还可以指定多个分隔符,作为同一个分隔符处理:
#tail-n3/etc/services|awk-F'[/#]''{print$3}'iqobjectiqobjectMatahariBroker#tail-n3/etc/services|awk-F'[/#]''{print$1}'iqobject48619iqobject48619matahari49000#tail-n3/etc/services|awk-F'[/#]''{print$2}'tcpudptcp#tail-n3/etc/services|awk-F'[/#]''{print$3}'iqobjectiqobjectMatahariBroker#tail-n3/etc/services|awk-F'[/]+''{print$2}'486194861949000
[]元字符的意思是符号其中任意一个字符,也就是说每遇到一个/或#时就分隔一个字段,当用多个分隔符时,就能更方面处理字段了。
3)变量赋值
#awk-va=123'BEGIN{printa}'123系统变量作为awk变量的值:#a=123#awk-va=$a'BEGIN{printa}'123或使用单引号#awk'BEGIN{print'$a'}'123
4)输出awk全局变量到文件
#seq5|awk--dump-variables'{print$0}'12345#catawkvars.outARGC:number(1)ARGIND:number(0)ARGV:array,1elementsBINMODE:number(0)CONVFMT:string("%.6g")ERRNO:number(0)FIELDWIDTHS:string("")FILENAME:string("-")FNR:number(5)FS:string("")IGNORECASE:number(0)LINT:number(0)NF:number(1)NR:number(5)OFMT:string("%.6g")OFS:string("")ORS:string("\n")RLENGTH:number(0)RS:string("\n")RSTART:number(0)RT:string("\n")SUBSEP:string("\034")TEXTDOMAIN:string("messages")
5)BEGIN和END
BEGIN模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标题。
例如:打印页眉
#tail/etc/services|awk'BEGIN{print"Service\t\tPort\t\t\tDescription\n==="}{print$0}'ServicePortDescription===3gpp-cbsp48049/tcp#3GPPCellBroadcastServiceisnetserv48128/tcp#ImageSystemsNetworkServicesisnetserv48128/udp#ImageSystemsNetworkServicesblp548129/tcp#Bloomberglocatorblp548129/udp#Bloomberglocatorcom-bardac-dw48556/tcp#com-bardac-dwcom-bardac-dw48556/udp#com-bardac-dwiqobject48619/tcp#iqobjectiqobject48619/udp#iqobjectmatahari49000/tcp#MatahariBroker
END模式是在程序处理完才会执行。
例如:打印页尾
#tail/etc/services|awk'{print$0}END{print"===\nEND......"}'3gpp-cbsp48049/tcp#3GPPCellBroadcastServiceisnetserv48128/tcp#ImageSystemsNetworkServicesisnetserv48128/udp#ImageSystemsNetworkServicesblp548129/tcp#Bloomberglocatorblp548129/udp#Bloomberglocatorcom-bardac-dw48556/tcp#com-bardac-dwcom-bardac-dw48556/udp#com-bardac-dwiqobject48619/tcp#iqobjectiqobject48619/udp#iqobjectmatahari49000/tcp#MatahariBroker===END......
6)格式化输出awk命令到文件
#tail/etc/services|awk--profile'BEGIN{print"Service\t\tPort\t\t\tDescription\n==="}{print$0}END{print"===\nEND......"}'ServicePortDescription===nimgtw48003/udp#NimbusGateway3gpp-cbsp48049/tcp#3GPPCellBroadcastServiceProtocolisnetserv48128/tcp#ImageSystemsNetworkServicesisnetserv48128/udp#ImageSystemsNetworkServicesblp548129/tcp#Bloomberglocatorblp548129/udp#Bloomberglocatorcom-bardac-dw48556/tcp#com-bardac-dwcom-bardac-dw48556/udp#com-bardac-dwiqobject48619/tcp#iqobjectiqobject48619/udp#iqobject===END......#catawkprof.out#gawkprofile,createdSatJan719:45:222017#BEGINblock(s)BEGIN{print"Service\t\tPort\t\t\tDescription\n==="}#Rule(s){print$0}#ENDblock(s)END{print"===\nEND......"}
7)/re/正则匹配
匹配包含tcp的行:#tail/etc/services|awk'/tcp/{print$0}'3gpp-cbsp48049/tcp#3GPPCellBroadcastServiceisnetserv48128/tcp#ImageSystemsNetworkServicesblp548129/tcp#Bloomberglocatorcom-bardac-dw48556/tcp#com-bardac-dwiqobject48619/tcp#iqobjectmatahari49000/tcp#MatahariBroker匹配开头是blp5的行:#tail/etc/services|awk'/^blp5/{print$0}'blp548129/tcp#Bloomberglocatorblp548129/udp#Bloomberglocator匹配第一个字段是8个字符的行:#tail/etc/services|awk'/^[a-z0-9]{8}/{print$0}'iqobject48619/tcp#iqobjectiqobject48619/udp#iqobjectmatahari49000/tcp#MatahariBroker
8)逻辑and、or和not
匹配记录中包含blp5和tcp的行:#tail/etc/services|awk'/blp5/&&/tcp/{print$0}'blp548129/tcp#Bloomberglocator匹配记录中包含blp5或tcp的行:#tail/etc/services|awk'/blp5/||/tcp/{print$0}'3gpp-cbsp48049/tcp#3GPPCellBroadcastServiceisnetserv48128/tcp#ImageSystemsNetworkServicesblp548129/tcp#Bloomberglocatorblp548129/udp#Bloomberglocatorcom-bardac-dw48556/tcp#com-bardac-dwiqobject48619/tcp#iqobjectmatahari49000/tcp#MatahariBroker不匹配开头是#和空行:#awk'!/^#/&&!/^$/{print$0}'/etc/httpd/conf/httpd.conf或#awk'!/^#|^$/'/etc/httpd/conf/httpd.conf或#awk'/^[^#]|"^$"/'/etc/httpd/conf/httpd.conf
9)匹配范围
#tail/etc/services|awk'/^blp5/,/^com/'blp548129/tcp#Bloomberglocatorblp548129/udp#Bloomberglocatorcom-bardac-dw48556/tcp#com-bardac-dw
博客地址:http://lizhenliang.blog.51cto.com
QQ群:323779636(Shell/Python运维开发群)
8.3.3 内置变量变量名
描述
FS
输入字段分隔符,默认是空格或制表符
OFS
输出字段分隔符,默认是空格
RS
输入记录分隔符,默认是换行符\n
ORS
输出记录分隔符,默认是换行符\n
NF
统计当前记录中字段个数
NR
统计记录编号,每处理一行记录,编号就会+1
FNR
统计记录编号,每处理一行记录,编号也会+1,与NR不同的是,处理第二个文件时,编号会重新计数。
ARGC
命令行参数数量
ARGIND
当前正在处理的文件索引值。第一个文件是1,第二个文件是2,以此类推
ARGV
命令行参数数组序列数组,下标从0开始,ARGV[0]是awk
ENVIRON
当前系统的环境变量
FILENAME
输出当前处理的文件名
IGNORECASE
忽略大小写
SUBSEP
数组中下标的分隔符,默认为"\034"
示例:
1)FS和OFS
在程序开始前重新赋值FS变量,改变默认分隔符为冒号,与-F一样。
#awk'BEGIN{FS=":"}{print$1,$2}'/etc/passwd|head-n5rootxbinxdaemonxadmxlpx也可以使用-v来重新赋值这个变量:#awk-vFS=':''{print$1,$2}'/etc/passwd|head-n5#中间逗号被换成了OFS的默认值rootxbinxdaemonxadmxlpx由于OFS默认以空格分隔,反向引用多个字段分隔的也是空格,如果想指定输出分隔符这样:#awk'BEGIN{FS=":";OFS=":"}{print$1,$2}'/etc/passwd|head-n5root:xbin:xdaemon:xadm:xlp:x也可以通过字符串拼接实现分隔:#awk'BEGIN{FS=":"}{print$1"#"$2}'/etc/passwd|head-n5root#xbin#xdaemon#xadm#xlp#x
2)RS和ORS
RS默认是\n分隔每行,如果想指定以某个字符作为分隔符来处理记录:
#echo"www.baidu.com/user/test.html"|awk'BEGIN{RS="/"}{print$0}'www.baidu.comusertest.htmlRS也支持正则,简单演示下:#seq-f"str%02g"10|sed'n;n;a\-----'|awk'BEGIN{RS="-+"}{print$1}'str01str04str07str10将输出的换行符替换为+号:#seq10|awk'BEGIN{ORS="+"}{print$0}'1+2+3+4+5+6+7+8+9+10+替换某个字符:#tail-n2/etc/services|awk'BEGIN{RS="/";ORS="#"}{print$0}'iqobject48619#udp#iqobjectmatahari49000#tcp#MatahariBroker
3)NF
NF是打印字段个数。
#echo"abcdef"|awk'{printNF}'6打印最后一个字段:#echo"abcdef"|awk'{print$NF}'f打印倒数第二个字段:#echo"abcdef"|awk'{print$(NF-1)}'e排除最后两个字段:#echo"abcdef"|awk'{$NF="";$(NF-1)="";print$0}'abcd排除第一个字段:#echo"abcdef"|awk'{$1="";print$0}'bcdef
4)NR和FNR
NR统计记录编号,每处理一行记录,编号就会+1,FNR不同的是在统计第二个文件时会重新计数。
打印行数:#tail-n5/etc/services|awk'{printNR,$0}'1com-bardac-dw48556/tcp#com-bardac-dw2com-bardac-dw48556/udp#com-bardac-dw3iqobject48619/tcp#iqobject4iqobject48619/udp#iqobject5matahari49000/tcp#MatahariBroker打印总行数:#tail-n5/etc/services|awk'END{printNR}'5打印第三行:#tail-n5/etc/services|awk'NR==3'iqobject48619/tcp#iqobject打印第三行第二个字段:#tail-n5/etc/services|awk'NR==3{print$2}'48619/tcp打印前三行:#tail-n5/etc/services|awk'NR<=3{printNR,$0}'1com-bardac-dw48556/tcp#com-bardac-dw2com-bardac-dw48556/udp#com-bardac-dw3iqobject48619/tcp#iqobject
看下NR和FNR的区别:
#cataabc#catbcde#awk'{printNR,FNR,$0}'ab11a22b33c41c52d63e
可以看出NR每处理一行就会+1,而FNR在处理第二个文件时,编号重新计数。同时也知道awk处理两个文件时,是合并到一起处理。
#awk'FNR==NR{print$0"1"}FNR!=NR{print$0"2"}'aba1b1c1c2d2e2
当FNR==NR时,说明在处理第一个文件内容,不等于时说明在处理第二个文件内容。
一般FNR在处理多个文件时会用到,下面会讲解。
5)ARGC和ARGV
ARGC是命令行参数数量
ARGV是将命令行参数存到数组,元素由ARGC指定,数组下标从0开始
#awk'BEGIN{printARGC}'1234#awk'BEGIN{printARGV[0]}'awk#awk'BEGIN{printARGV[1]}'121#awk'BEGIN{printARGV[2]}'122
6)ARGIND
ARGIND是当前正在处理的文件索引值,第一个文件是1,第二个文件是2,以此类推,从而可以通过这种方式判断正在处理哪个文件。
#awk'{printARGIND,$0}'ab1a1b1c2c2d2e#awk'ARGIND==1{print"a->"$0}ARGIND==2{print"b->"$0}'aba->aa->ba->cb->cb->db->e
7)ENVIRON
ENVIRON调用系统变量。
#awk'BEGIN{printENVIRON["HOME"]}'/root如果是设置的环境变量,还需要用export导入到系统变量才可以调用:#awk'BEGIN{printENVIRON["a"]}'#exporta#awk'BEGIN{printENVIRON["a"]}'123
8)FILENAME
FILENAME是当前处理文件的文件名。
#awk'FNR==NR{printFILENAME"->"$0}FNR!=NR{printFILENAME"->"$0}'aba->aa->ba->cb->cb->db->e9)忽略大小写#echo"Aabc"|xargs-n1|awk'BEGIN{IGNORECASE=1}/a/'Aa
等于1代表忽略大小写。
博客地址:http://lizhenliang.blog.51cto.com
QQ群:323779636(Shell/Python运维开发群)
8.3.4 操作符运算符
描述
(....)
分组
$
字段引用
++ --
递增和递减
+ - !
加号,减号,和逻辑否定
* / %
乘,除和取余
+ -
加法,减法
| |&
管道,用于getline,print和printf
< > <= >= != ==
关系运算符
~ !~
正则表达式匹配,否定正则表达式匹配
in
数组成员
&& ||
逻辑and,逻辑or
?:
简写条件表达式:
expr1 ? expr2 : expr3
第一个表达式为真,执行expr2,否则执行expr3
= += -= *= /= %= ^=
变量赋值运算符
须知:在awk中,有3种情况表达式为假:数字是0,空字符串和未定义的值
数值运算,未定义变量初始值为0。字符运算,未定义变量初始值为空。
举例测试:
#awk'BEGIN{n=0;if(n)print"true";elseprint"false"}'false#awk'BEGIN{s="";if(s)print"true";elseprint"false"}'false#awk'BEGIN{if(s)print"true";elseprint"false"}'false
示例:
1)截取整数
#echo"123abcabc123123abc123"|xargs-n1|awk'{print+$0}'1230123#echo"123abcabc123123abc123"|xargs-n1|awk'{print-$0}'-1230-123
2)感叹号
打印奇数行:#seq6|awk'i=!i'135读取第一行,i是未定义变量,也就是i=!0,!取反意思。感叹号右边是个布尔值,0或空字符串为假,非0或非空字符串为真,!0就是真,因此i=1,条件为真打印当前记录。没有print为什么会打印呢?因为模式后面没有动作,默认会打印整条记录。读取第二行,因为上次i的值由0变成了1,此时就是i=!1,条件为假不打印。读取第三行,上次条件又为假,i恢复初始值0,取反,继续打印。以此类推...可以看出,运算时并没有判断行内容,而是利用布尔值真假判断输出当前行。打印偶数行:#seq6|awk'!(i=!i)'246
2)不匹配某行
#tail/etc/services|awk'!/blp5/{print$0}'3gpp-cbsp48049/tcp#3GPPCellBroadcastServiceisnetserv48128/tcp#ImageSystemsNetworkServicesisnetserv48128/udp#ImageSystemsNetworkServicescom-bardac-dw48556/tcp#com-bardac-dwcom-bardac-dw48556/udp#com-bardac-dwiqobject48619/tcp#iqobjectiqobject48619/udp#iqobjectmatahari49000/tcp#MatahariBroker
3)乘法和除法
#seq5|awk'{print$0*2}'246810#seq5|awk'{print$0%2}'10101打印偶数行:#seq5|awk'$0%2==0{print$0}'24打印奇数行:#seq5|awk'$0%2!=0{print$0}'135
4)管道符使用
#seq5|shuf|awk'{print$0|"sort"}'12345
5)正则表达式匹配
#seq5|awk'$0~3{print$0}'3#seq5|awk'$0!~3{print$0}'1245#seq5|awk'$0~/[34]/{print$0}'34#seq5|awk'$0!~/[34]/{print$0}'125#seq5|awk'$0~/[^34]/{print$0}'125
6)判断数组成员
#awk'BEGIN{a["a"]=123}END{if("a"ina)print"yes"}'</dev/nullyes
7)三目运算符
#awk'BEGIN{print1==1?"yes":"no"}'#三目运算作为一个表达式,里面不允许写printyes#seq3|awk'{print$0==2?"yes":"no"}'noyesno替换换行符为逗号:#seq5|awk'{printn=(n?n","$0:$0)}'11,21,2,31,2,3,41,2,3,4,5#seq5|awk'{n=(n?n","$0:$0)}END{printn}'1,2,3,4,5说明:读取第一行时,n没有变量,为假输出$0也就是1,并赋值变量n,读取第二行时,n是1为真,输出1,2以此类推,后面会一直为真。每三行后面添加新一行:#seq10|awk'{printNR%3?$0:$0"\ntxt"}'123txt456txt789txt10在两行合并一行:#seq6|awk'{printfNR%2!=0?$0"":$0"\n"}'123456#seq6|awk'ORS=NR%2?"":"\n"'123456#seq6|awk'{if(NR%2)ORS="";elseORS="\n";print}'
8)变量赋值
字段求和:#seq5|awk'{sum+=1}END{printsum}'5#seq5|awk'{sum+=$0}END{printsum}'158.3.5 流程控制
1)if语句
格式:if(condition) statement [ else statement ]
单分支:#seq5|awk'{if($0==3)print$0}'3双分支:#seq5|awk'{if($0==3)print$0;elseprint"no"}'nono3nono多分支:#catfile123456789#awk'{if($1==4){print"1"}elseif($2==5){print"2"}elseif($3==6){print"3"}else{print"no"}}'fileno1no
2)while语句
格式:while(condition) statement
遍历打印所有字段:#awk'{i=1;while(i<=NF){print$i;i++}}'file123456789awk是按行处理的,每次读取一行,并遍历打印每个字段。
3)for语句C语言风格
格式:for(expr1; expr2; expr3) statement
遍历打印所有字段:#catfile123456789#awk'{for(i=1;i<=NF;i++)print$i}'file123456789倒叙打印文本:#awk'{for(i=NF;i>=1;i--)print$i}'file321654987都换行了,这并不是我们要的结果。怎么改进呢?#awk'{for(i=NF;i>=1;i--){printf$i""};print""}'file#print本身就会新打印一行321654987或#awk'{for(i=NF;i>=1;i--)if(i==1)printf$i"\n";elseprintf$i""}'file321654654987在这种情况下,是不是就排除第一行和倒数第一行呢?我们正序打印看下排除第一行:#awk'{for(i=2;i<=NF;i++){printf$i""};print""}'file235689排除第二行:#awk'{for(i=1;i<=NF-1;i++){printf$i""};print""}'file124578IP加单引号:#echo'10.10.10.110.10.10.210.10.10.3'|awk'{for(i=1;i<=NF;i++)printf"\047"$i"\047"}'10.10.10.1''10.10.10.2''10.10.10.3'\047是ASCII码,可以通过showkey-a命令查看。4)for语句遍历数组格式:for(varinarray)statement#seq-f"str%.g"5|awk'{a[NR]=$0}END{for(vina)printv,a[v]}'4str45str51str12str23str3
5)break和continue语句
break跳过所有循环,continue跳过当前循环。
#awk'BEGIN{for(i=1;i<=5;i++){if(i==3){break};printi}}'12#awk'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};printi}}'1245
6)删除数组和元素
格式:
deletearray[index] 删除数组元素
deletearray 删除数组
#seq-f"str%.g"5|awk'{a[NR]=$0}END{deletea;for(vina)printv,a[v]}'空的…#seq-f"str%.g"5|awk'{a[NR]=$0}END{deletea[3];for(vina)printv,a[v]}'4str45str51str12str2
7)exit语句
格式:exit[ expression ]
exit退出程序,与shell的exit一样。[ expr]是0-255之间的数字。
#seq5|awk'{if($0~/3/)exit(123)}'#echo$?1238.3.6 数组
数组是用来存储一系列值的变量,通过下标(索引)来访问值。
awk中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标。
数组元素的键和值存储在awk程序内部的一个表中,该表采用散列算法,因此数组元素是随机排序。
数组格式:array[index]=value
1)自定义数组
#awk'BEGIN{a[0]="test";printa[0]}'test
2)通过NR设置记录下标,下标从1开始
#tail-n3/etc/passwd|awk-F:'{a[NR]=$1}END{printa[1]}'systemd-network#tail-n3/etc/passwd|awk-F:'{a[NR]=$1}END{printa[2]}'zabbix#tail-n3/etc/passwd|awk-F:'{a[NR]=$1}END{printa[3]}'user
3)通过for循环遍历数组
#tail-n5/etc/passwd|awk-F:'{a[NR]=$1}END{for(vina)printa[v],v}'zabbix4user5admin1systemd-bus-proxy2systemd-network3#tail-n5/etc/passwd|awk-F:'{a[NR]=$1}END{for(i=1;i<=NR;i++)printa[i],i}'admin1systemd-bus-proxy2systemd-network3zabbix4user5
上面打印的i是数组的下标。
第一种for循环的结果是乱序的,刚说过,数组是无序存储。
第二种for循环通过下标获取的情况是排序正常。
所以当下标是数字序列时,还是用for(expr1;expr2;expr3)循环表达式比较好,保持顺序不变。
4)通过++方式作为下标
#tail-n5/etc/passwd|awk-F:'{a[x++]=$1}END{for(i=0;i<=x-1;i++)printa[i],i}'admin0systemd-bus-proxy1systemd-network2zabbix3user4
x被awk初始化值是0,没循环一次+1
5)使用字段作为下标
#tail-n5/etc/passwd|awk-F:'{a[$1]=$7}END{for(vina)printa[v],v}'/sbin/nologinadmin/bin/bashuser/sbin/nologinsystemd-network/sbin/nologinsystemd-bus-proxy/sbin/nologinzabbix
6)统计相同字段出现次数
#tail/etc/services|awk'{a[$1]++}END{for(vina)printa[v],v}'2com-bardac-dw13gpp-cbsp2iqobject1matahari2isnetserv2blp5#tail/etc/services|awk'{a[$1]+=1}END{for(vina)printa[v],v}'2com-bardac-dw13gpp-cbsp2iqobject1matahari2isnetserv2blp5#tail/etc/services|awk'/blp5/{a[$1]++}END{for(vina)printa[v],v}'2blp5
第一个字段作为下标,值被++初始化是0,每次遇到下标(第一个字段)一样时,对应的值就会被+1,因此实现了统计出现次数。
想要实现去重的的话就简单了,只要打印下标即可。
7)统计TCP连接状态
#netstat-antp|awk'/^tcp/{a[$6]++}END{for(vina)printa[v],v}'9LISTEN6ESTABLISHED6TIME_WAIT
8)只打印出现次数大于等于2的
#tail/etc/services|awk'{a[$1]++}END{for(vina)if(a[v]>=2){printa[v],v}}'2com-bardac-dw2iqobject2isnetserv2blp5
9)去重
只打印重复的行:#tail/etc/services|awk'a[$1]++'isnetserv48128/udp#ImageSystemsNetworkServicesblp548129/udp#Bloomberglocatorcom-bardac-dw48556/udp#com-bardac-dwiqobject48619/udp#iqobject去重:#tail/etc/services|awk'!a[$1]++'3gpp-cbsp48049/tcp#3GPPCellBroadcastServiceisnetserv48128/tcp#ImageSystemsNetworkServicesblp548129/tcp#Bloomberglocatorcom-bardac-dw48556/tcp#com-bardac-dwiqobject48619/tcp#iqobjectmatahari49000/tcp#MatahariBroker
只打印重复的行说明:先明白一个情况,当值是0是为假,1为真,知道这点就不难理解了。由于执行了++当处理第一条记录时,初始值是0为假,就不打印,如果再遇到相同的记录,值就会+1,不为0,打印。
去重说明:初始值是0为假,感叹号取反为真,打印,也就是说,每个记录的第一个值都是为0,所以都会打印,如果再遇到相同的记录+1,值就会为真,取反为假就不打印。
#tail/etc/services|awk'{if(a[$1]++)print$1}'isnetservblp5com-bardac-dwiqobject使用三目运算:#tail/etc/services|awk'{printa[$1]++?$1:"no"}'nonoisnetservnoblp5nocom-bardac-dwnoiqobjectno#tail/etc/services|awk'{if(!a[$1]++)print$1}'3gpp-cbspisnetservblp5com-bardac-dwiqobjectmatahari
10)统计每个相同字段的某字段总数:
#tail/etc/services|awk-F'[/]+''{a[$1]+=$2}END{for(vina)printv,a[v]}'com-bardac-dw971123gpp-cbsp48049iqobject97238matahari49000isnetserv96256blp596258
11)多维数组
awk的多维数组,实际上awk并不支持多维数组,而是逻辑上模拟二维数组的访问方式,比如a[a,b]=1,使用SUBSEP(默认\034)作为分隔下标字段,存储后是这样a\034b。
示例:
#awk'BEGIN{a["x","y"]=123;for(vina)printv,a[v]}'xy123我们可以重新复制SUBSEP变量,改变下标默认分隔符:#awk'BEGIN{SUBSEP=":";a["x","y"]=123;for(vina)printv,a[v]}'x:y123根据指定的字段统计出现次数:#cataA192.168.1.1HTTPB192.168.1.2HTTPB192.168.1.2MYSQLC192.168.1.1MYSQLC192.168.1.1MQD192.168.1.4NGINX#awk'BEGIN{SUBSEP="-"}{a[$1,$2]++}END{for(vina)printa[v],v}'a1D-192.168.1.41A-192.168.1.12C-192.168.1.12B-192.168.1.2
博客地址:http://lizhenliang.blog.51cto.com
QQ群:323779636(Shell/Python运维开发群)
8.3.7 内置函数函数
描述
int(expr)
截断为整数
sqrt(expr)
平方根
rand()
返回一个随机数N,0和1范围,0 < N < 1
srand([expr])
使用expr生成随机数,如果不指定,默认使用当前时间为种子,如果前面有种子则使用生成随机数
asort(a, b)
对数组a的值进行排序,把排序后的值存到新的数组b中,新排序的数组下标从1开始
asorti(a,b)
对数组a的下标进行排序,同上
sub(r, s [, t])
对输入的记录用s替换r,t可选针对某字段替换,但只替换第一个字符串
gsub(r,s [, t])
对输入的记录用s替换r,t可选针对某字段替换,替换所有字符串
index(s, t)
返回s中字符串t的索引位置,0为不存在
length([s])
返回s的长度
match(s, r [, a])
测试字符串s是否包含匹配r的字符串
split(s, a [, r [, seps] ])
根据分隔符seps将s分成数组a
substr(s, i [, n])
截取字符串s从i开始到长度n,如果n没指定则是剩余部分
tolower(str)
str中的所有大写转换成小写
toupper(str)
str中的所有小写转换成大写
systime()
当前时间戳
strftime([format [, timestamp[, utc-flag]]])
格式化输出时间,将时间戳转为字符串
示例:
1)int()
#echo"123abcabc123123abc123"|xargs-n1|awk'{printint($0)}'1230123#awk'BEGIN{printint(10/3)}'3
2)sqrt()
获取9的平方根:
#awk'BEGIN{printsqrt(9)}'3
3)rand()和srand()
rand()并不是每次运行就是一个随机数,会一直保持一个不变:#awk'BEGIN{printrand()}'0.237788当执行srand()函数后,rand()才会发生变化,所以一般在awk着两个函数结合生成随机数,但是也有很大几率生成一样:#awk'BEGIN{srand();printrand()}'0.31687如果想生成1-10的随机数可以这样:#awk'BEGIN{srand();printint(rand()*10)}'4
如果想更完美生成随机数,还得做相应的处理!
4)asort()和asorti()
#seq-f"str%.g"5|awk'{a[x++]=$0}END{s=asort(a,b);for(i=1;i<=s;i++)printb[i],i}'str11str22str33str44str55#seq-f"str%.g"5|awk'{a[x++]=$0}END{s=asorti(a,b);for(i=1;i<=s;i++)printb[i],i}'0112233445
asort将a数组的值放到数组b,a下标丢弃,并将数组b的总行号赋值给s,新数组b下标从1开始,然后遍历。
5)sub()和gsub()
#tail/etc/services|awk'/blp5/{sub(/tcp/,"icmp");print$0}'blp548129/icmp#Bloomberglocatorblp548129/udp#Bloomberglocator#tail/etc/services|awk'/blp5/{gsub(/c/,"9");print$0}'blp548129/t9p#Bloomberglo9atorblp548129/udp#Bloomberglo9ator#echo"122345"|awk'gsub(2,7,$2){print$0}'172345#echo"123abc"|awk'gsub(/[0-9]/,'0'){print$0}'000abc
在指定行前后加一行:
#seq5|awk'NR==2{sub('/.*/',"txt\n&")}{print}'1txt2345#seq5|awk'NR==2{sub('/.*/',"&\ntxt")}{print}'12txt345
6)index()
#tail-n5/etc/services|awk'{printindex($2,"tcp")}'70707
7)length()
#tail-n5/etc/services|awk'{printlength($2)}'99999统计数组的长度:#tail-n5/etc/services|awk'{a[$1]=$2}END{printlength(a)}'3
8)split()
#echo-e"123#456#789\nabc#cde#fgh"|awk'{split($0,a);for(vina)printa[v],v}'123#456#7891abc#cde#fgh1#echo-e"123#456#789\nabc#cde#fgh"|awk'{split($0,a,"#");for(vina)printa[v],v}'123145627893abc1cde2fgh3
9)substr()
#echo-e"123#456#789\nabc#cde#fgh"|awk'{printsubstr($0,4)}'#456#789#cde#fgh#echo-e"123#456#789\nabc#cde#fgh"|awk'{printsubstr($0,4,5)}'#456##cde#
10)tolower()和toupper()
#echo-e"123#456#789\nABC#cde#fgh"|awk'{printtolower($0)}'123#456#789abc#cde#fgh#echo-e"123#456#789\nabc#cde#fgh"|awk'{printtoupper($0)}'123#456#789ABC#CDE#FGH
11)时间处理
返回当前时间戳:#awk'BEGIN{printsystime()}'1483297766将时间戳转为日期和时间#echo"1483297766"|awk'{printstrftime("%Y-%m-%d%H:%M:%S",$0)}'2017-01-0114:09:268.3.8 I/O语句
语句
描述
getline
设置$0来自下一个输入记录
getline var
设置var来自下一个输入记录
command | getline [var]
运行命令管道输出到$0或var
next
停止当前处理的输入记录
打印当前记录
printf fmt, expr-list
格式化输出
printf fmt, expr-list >file
格式输出和写到文件
system(cmd-line)
执行命令和返回状态
print ... >> file
追加输出到文件
print ... | command
打印输出作为命令输入
示例:
1)getline
获取匹配的下一行:#seq5|awk'/3/{getline;print}'4#seq5|awk'/3/{print;getline;print}'34在匹配的下一行加个星号:#seq5|awk'/3/{getline;sub(".*","&*");print}'4*#seq5|awk'/3/{print;getline;sub(".*","&*")}{print}'1234*5
2)getline var
把a文件的行追加到b文件的行尾:#cataabc#catb1one2two3three#awk'{getlineline<"a";print$0,line}'b1onea2twob3threec把a文件的行替换b文件的指定字段:#awk'{getlineline<"a";gsub($2,line,$2);print}'b1a2b3c把a文件的行替换b文件的对应字段:#awk'{getlineline<"a";gsub("two",line,$2);print}'b1one2b3three3)command|getline[var]获取执行shell命令后结果的第一行:#awk'BEGIN{"seq5"|getlinevar;printvar}'1循环输出执行shell命令后的结果:#awk'BEGIN{while("seq5"|getline)print}'12345
4)next
不打印匹配行:#seq5|awk'{if($0==3){next}else{print}}'1245删除指定行:#seq5|awk'NR==1{next}{print$0}'2345如果前面动作成功,就遇到next,后面的动作不再执行,跳过。或者:#seq5|awk'NR!=1{print}'2345把第一行内容放到每行的前面:#catahello1a2b3c#awk'NR==1{s=$0;next}{prints,$0}'ahello1ahello2bhello3c#awk'NR==1{s=$0}NF!=1{prints,$0}'ahello1ahello2bhello3c
5)system()
执行shell命令判断返回值:#awk'BEGIN{if(system("greproot/etc/passwd&>/dev/null")==0)print"yes";elseprint"no"}'yes
6)打印结果写到文件
#tail-n5/etc/services|awk'{print$2>"a.txt"}'#cata.txt48049/tcp48128/tcp48128/udp48129/tcp48129/udp
7)管道连接shell命令
将结果通过grep命令过滤:#tail-n5/etc/services|awk'{print$2|"greptcp"}'48556/tcp48619/tcp49000/tcp8.3.9 printf语句
格式化输出,默认打印字符串不换行。
格式:printf [format]arguments
Format
描述
%s一个字符串
%d,%i一个小数
%f一个浮点数
%.ns
输出字符串,n是输出几个字符
%ni
输出整数,n是输出几个数字
%m.nf
输出浮点数,m是输出整数位数,n是输出的小数位数
%x
不带正负号的十六进制,使用a至f表示10到15
%X
不带正负号的十六进制,使用A至F表示10至15
%%
输出单个%
%-5s
左对齐,对参数每个字段左对齐,宽度为5
%-4.2f
左对齐,宽度为4,保留两位小数
%5s
右对齐,不加横线表示右对齐
示例:
将换行符换成逗号:#seq5|awk'{if($0!=5)printf"%s,",$0;elseprint$0}'1,2,3,4,5小括号中的5是最后一个数字。输出一个字符:#awk'BEGIN{printf"%.1s\n","abc"}'a保留一个小数点:#awk'BEGIN{printf"%.2f\n",10/3}'3.33格式化输出:#awk'BEGIN{printf"user:%s\tpass:%d\n","abc",123}'user:abcpass:123左对齐宽度10:#awk'BEGIN{printf"%-10s%-10s%-10s\n","ID","Name","Passwd"}'IDNamePasswd右对齐宽度10:#awk'BEGIN{printf"%10s%10s%10s\n","ID","Name","Passwd"}'IDNamePasswd打印表格:#vitest.awkBEGIN{print"+--------------------+--------------------+";printf"|%-20s|%-20s|\n","Name","Number";print"+--------------------+--------------------+";}#awk-ftest.awk+--------------------+--------------------+|Name|Number|+--------------------+--------------------+格式化输出:#awk-F:'BEGIN{printf"UserName\t\tShell\n-----------------------------\n"}{printf"%-20s%-20s\n",$1,$7}END{print"END...\n"}'/etc/passwd打印十六进制:#awk'BEGIN{printf"%x%X",123,123}'7b7B8.3.10 自定义函数
格式:function name(parameter list) { statements }
示例:
#awk'functionmyfunc(a,b){returna+b}BEGIN{printmyfunc(1,2)}'3
博客地址:http://lizhenliang.blog.51cto.com
QQ群:323779636(Shell/Python运维开发群)
8.3.11 需求案例1)分析Nginx日志
日志格式:'$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(vina)printv,a[v]}'access.log统计访问访问大于100次的IP:#awk'{a[$1]++}END{for(vina){if(a[v]>100)printv,a[v]}}'access.log统计访问IP次数并排序取前10:#awk'{a[$1]++}END{for(vina)printv,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(vina)printv,a[v]}'access.log统计上一分钟访问量:#date=$(date-d'-1minute'+%d/%d/%Y:%H:%M)#awk-vdate=$date'$4~date{c++}END{printc}'access.log统计访问最多的10个页面:#awk'{a[$7]++}END{for(vina)printv,a[v]|"sort-k1-nr|head-n10"}'access.log统计每个URL数量和返回内容总大小:#awk'{a[$7]++;size[$7]+=$10}END{for(vina)printa[v],v,size[v]}'access.log统计每个IP访问状态码数量:#awk'{a[$1""$9]++}END{for(vina)printv,a[v]}'access.log统计访问IP是404状态次数:#awk'{if($9~/404/)a[$1""$9]++}END{for(iina)printv,a[v]}'access.log
2)两个文件对比
找出b文件在a文件相同记录:
#seq15>a#seq37>b方法1:#awk'FNR==NR{a[$0];next}{if($0ina)print$0}'ab345#awk'FNR==NR{a[$0];next}{if($0ina)printFILENAME,$0}'abb3b4b5#awk'FNR==NR{a[$0]}NR>FNR{if($0ina)print$0}'ab345#awk'FNR==NR{a[$0]=1;next}(a[$0]==1)'ab#a[$0]是通过b文件每行获取值,如果是1说明有#awk'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}'ab345方法2:#awk'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0ina)print$0}'ab345方法3:#awk'ARGIND==1{a[$0]=1}ARGIND==2&&a[$0]==1'ab345找出b文件在a文件不同记录:方法1:#awk'FNR==NR{a[$0];next}!($0ina)'ab67#awk'FNR==NR{a[$0]=1;next}(a[$0]!=1)'ab#awk'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}'ab67方法2:#awk'FILENAME=="a"{a[$0]=1}FILENAME=="b"&&a[$0]!=1'ab方法3:#awk'ARGIND==1{a[$0]=1}ARGIND==2&&a[$0]!=1'ab
3)合并两个文件
将a文件合并到b文件:
#catazhangsan20lisi23wangwu29#catbzhangsanmanlisiwomanwangwuman#awk'FNR==NR{a[$1]=$0;next}{printa[$1],$2}'abzhangsan20manlisi23womanwangwu29man#awk'FNR==NR{a[$1]=$0}NR>FNR{printa[$1],$2}'abzhangsan20manlisi23womanwangwu29man
将a文件相同IP的服务名合并:
#cata192.168.1.1:httpd192.168.1.1:tomcat192.168.1.2:httpd192.168.1.2:postfix192.168.1.3:mysqld192.168.1.4:httpd#awk'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1]$2}END{for(vina)printv,a[v]}'a192.168.1.4:httpd192.168.1.1:httpdtomcat192.168.1.2:httpdpostfix192.168.1.3:mysqld
说明:数组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。每次遇到相同的下标(第一个字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。
4)将第一列合并到一行
#catfile123456789#awk'{for(i=1;i<=NF;i++)a[i]=a[i]$i""}END{for(vina)printa[v]}'file147258369
说明:
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[2]与上面一样,为空,a[3]=3 。
读取第二行时:
第一个字段:a[1]=a[1]4" " 值a[2]获取数组a的2为下标对应的值,上面已经有这个下标了,对应的值是1,因此a[1]=1 4
第二个字段:a[2]=a[2]5" " 同上,a[2]=2 5
第三个字段:a[3]=a[3]6" " 同上,a[2]=3 6
读取第三行时处理方式同上,数组最后还是三个下标,分别是1=1 4 7,2=2 5 8,3=36 9。最后for循环输出所有下标值。
5)字符串拆分,统计出现的次数
字符串拆分:
方法1:#echo"helloworld"|awk-F'''{print$1}'h#echo"hello"|awk-F'''{for(i=1;i<=NF;i++)print$i}'hello方法2:#echo"hello"|awk'{split($0,a,"''");for(vina)printa[v]}'lohel
统计字符串中每个字母出现的次数:
#echo"a.b.c,c.d.e"|awk-F'[.,]''{for(i=1;i<=NF;i++)a[$i]++}END{for(vina)printv,a[v]}'a1b1c2d1e1
5)费用统计
#catazhangsan80001zhangsan50001lisi10001lisi20001wangwu15001zhaoliu60001zhaoliu20001zhaoliu30001#awk'{name[$1]++;cost[$1]+=$2;number[$1]+=$3}END{for(vinname)printv,cost[v],number[v]}'azhangsan50001lisi30002wangwu15001zhaoliu110003
6)获取数字字段最大值
#cataab1cd2ef3gh3ij2获取第三字段最大值:#awk'BEGIN{max=0}{if($3>max)max=$3}END{printmax}'a3打印第三字段最大行:#awk'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(vina)printv,a[v],max}'agh333ef333cd223ab113ij223#awk'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(vina)if(a[v]==max)printv}'agh3ef3
7)去除第一行和最后一行
#seq5|awk'NR>2{prints}{s=$0}'234
读取第一行,NR=1,不执行print s,s=1
读取第二行,NR=2,不执行print s,s=2 (大于为真)
读取第三行,NR=3,执行print s,此时s是上一次p赋值内容2,s=3
最后一行,执行print s,打印倒数第二行,s=最后一行
获取Nginx负载均衡配置端IP和端口:
#cataupstreamexample-servers1{server127.0.0.1:80weight=1max_fails=2fail_timeout=30s;}upstreamexample-servers2{server127.0.0.1:80weight=1max_fails=2fail_timeout=30s;server127.0.0.1:82backup;}#awk'/example-servers1/,/}/{if(NR>2){prints}{s=$2}}'a127.0.0.1:80#awk'/example-servers1/,/}/{if(i>1)prints;s=$2;i++}'a#awk'/example-servers1/,/}/{if(i>1){prints}{s=$2;i++}}'a127.0.0.1:80
读取第一行,i初始值为0,0>1为假,不执行print s,x=example-servers1,i=1
读取第二行,i=1,1>1为假,不执行prints,s=127.0.0.1:80,i=2
读取第三行,i=2,2>1为真,执行prints,此时s是上一次s赋值内容127.0.0.1:80,i=3
最后一行,执行print s,打印倒数第二行,s=最后一行。
这种方式与上面一样,只是用i++作为计数器。
好了,AWK大部分知识点都在这了,能否提高你逼格呢?
如有问题欢迎加入Python运维开发群(249171211)
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。