Shell文本处理三剑客之awk
转自:http://blog.51cto.com/lizhenliang/1892112本章大纲:
8.3 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=
把awk命令时的全局变量写入文件,
默认文件是awkvars.out
--profile=
格式化awk语句到文件,默认是awkprof.out
8.3.2 模式
常用模式有:
Pattern
Description
BEGIN{ }
给程序赋予初始状态,先执行的工作
END{ }
程序结束之后执行的一些扫尾工作
/regularexpression/
为每个输入记录匹配正则表达式
pattern &&pattern
逻辑and,满足两个模式
pattern || pattern
逻辑or,满足其中一个模式
! pattern
逻辑not,不满足模式
pattern1, pattern2
范围模式,匹配所有模式1的记录,直到匹配到模式2
而动作呢,就是下面所讲的print、流程控制、I/O语句等。
示例:
1)从文件读取awk程序处理文件
123456# vi test.awk{print$2}# tail -n3 /etc/services |awk -f test.awk48049/tcp48128/tcp49000/tcp 2)指定分隔符,打印指定字段
1234567891011121314打印第二字段,默认以空格分隔:# tail -n3 /etc/services |awk '{print $2}'48049/tcp48128/tcp48128/udp指定冒号为分隔符打印第一字段:# awk-F ':' '{print $1}' /etc/passwdrootbindaemonadmlpsync...... 还可以指定多个分隔符,作为同一个分隔符处理:
1234567891011121314151617181920# tail -n3 /etc/services |awk -F'[/#]' '{print $3}' iqobject iqobject MatahariBroker# tail -n3 /etc/services |awk -F'[/#]' '{print $1}'iqobject 48619iqobject 48619matahari 49000# tail -n3 /etc/services |awk -F'[/#]' '{print $2}'tcp udp tcp # tail -n3 /etc/services |awk -F'[/#]' '{print $3}' iqobject iqobject MatahariBroker# tail -n3 /etc/services |awk -F'[ /]+' '{print $2}'486194861949000 []元字符的意思是符号其中任意一个字符,也就是说每遇到一个/或#时就分隔一个字段,当用多个分隔符时,就能更方面处理字段了。
3)变量赋值
123456789# awk-v a=123 'BEGIN{print a}' 123系统变量作为awk变量的值:#a=123# awk-v a=$a 'BEGIN{print a}' 123或使用单引号# awk'BEGIN{print '$a'}' 123 4)输出awk全局变量到文件
123456789101112131415161718192021222324252627282930# seq 5|awk --dump-variables '{print $0}'12345# cat awkvars.out ARGC:number (1)ARGIND:number (0)ARGV:array, 1 elementsBINMODE: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模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标题。
例如:打印页眉
12345678910111213# tail /etc/services |awk 'BEGIN{print"Service\t\tPort\t\t\tDescription\n==="}{print $0}'Service Port Description===3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service isnetserv 48128/tcp # Image Systems Network Servicesisnetserv 48128/udp # Image Systems Network Servicesblp5 48129/tcp # Bloomberg locatorblp5 48129/udp # Bloomberg locatorcom-bardac-dw 48556/tcp # com-bardac-dwcom-bardac-dw 48556/udp # com-bardac-dwiqobject 48619/tcp #iqobjectiqobject 48619/udp # iqobjectmatahari 49000/tcp # Matahari Broker END模式是在程序处理完才会执行。
例如:打印页尾
12345678910111213# tail /etc/services |awk '{print $0}END{print "===\nEND......"}'3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service isnetserv 48128/tcp # Image Systems Network Servicesisnetserv 48128/udp # Image Systems Network Servicesblp5 48129/tcp # Bloomberg locatorblp5 48129/udp # Bloomberg locatorcom-bardac-dw 48556/tcp # com-bardac-dwcom-bardac-dw 48556/udp # com-bardac-dwiqobject 48619/tcp # iqobjectiqobject 48619/udp # iqobjectmatahari 49000/tcp # Matahari Broker===END...... 6)格式化输出awk命令到文件
1234567891011121314151617181920212223242526272829303132333435# tail /etc/services |awk --profile 'BEGIN{print"Service\t\tPort\t\t\tDescription\n==="}{print $0}END{print"===\nEND......"}'Service Port Description===nimgtw 48003/udp # Nimbus Gateway3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast ServiceProtocolisnetserv 48128/tcp # Image Systems Network Servicesisnetserv 48128/udp # Image Systems Network Servicesblp5 48129/tcp # Bloomberg locatorblp5 48129/udp # Bloomberg locatorcom-bardac-dw 48556/tcp # com-bardac-dwcom-bardac-dw 48556/udp # com-bardac-dwiqobject 48619/tcp # iqobjectiqobject 48619/udp # iqobject===END......# cat awkprof.out # gawk profile, created Sat Jan7 19:45:22 2017 # BEGIN block(s) BEGIN { print"Service\t\tPort\t\t\tDescription\n===" } # Rule(s) { print $0 } # END block(s) END { print "===\nEND......" } 7)/re/正则匹配
1234567891011121314151617匹配包含tcp的行:# tail /etc/services |awk '/tcp/{print $0}' 3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service isnetserv 48128/tcp # Image Systems Network Servicesblp5 48129/tcp # Bloomberg locatorcom-bardac-dw 48556/tcp # com-bardac-dwiqobject 48619/tcp # iqobjectmatahari 49000/tcp # Matahari Broker匹配开头是blp5的行:# tail /etc/services |awk '/^blp5/{print $0}' blp5 48129/tcp # Bloomberg locatorblp5 48129/udp # Bloomberg locator匹配第一个字段是8个字符的行:# tail /etc/services |awk '/^{8} /{print $0}'iqobject 48619/tcp # iqobjectiqobject 48619/udp # iqobjectmatahari 49000/tcp # Matahari Broker 8)逻辑and、or和not
123456789101112131415161718匹配记录中包含blp5和tcp的行:#tail /etc/services |awk '/blp5/ && /tcp/{print $0}' blp5 48129/tcp # Bloomberg locator匹配记录中包含blp5或tcp的行:#tail /etc/services |awk '/blp5/ || /tcp/{print $0}' 3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service isnetserv 48128/tcp # Image Systems Network Servicesblp5 48129/tcp # Bloomberg locatorblp5 48129/udp # Bloomberg locatorcom-bardac-dw 48556/tcp # com-bardac-dwiqobject 48619/tcp # iqobjectmatahari 49000/tcp # Matahari Broker不匹配开头是#和空行:# awk'! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf或# awk'! /^#|^$/' /etc/httpd/conf/httpd.conf或# awk'/^[^#]|"^$"/' /etc/httpd/conf/httpd.conf 9)匹配范围
1234# tail /etc/services |awk '/^blp5/,/^com/'blp5 48129/tcp # Bloomberg locatorblp5 48129/udp # Bloomberg locatorcom-bardac-dw 48556/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是awk
ENVIRON
当前系统的环境变量
FILENAME
输出当前处理的文件名
IGNORECASE
忽略大小写
SUBSEP
数组中下标的分隔符,默认为"\034"
示例:
1)FS和OFS
在程序开始前重新赋值FS变量,改变默认分隔符为冒号,与-F一样。
123456789101112131415161718192021222324252627# awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5 rootxbin xdaemonxadm xlp x也可以使用-v来重新赋值这个变量:# awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5 # 中间逗号被换成了OFS的默认值 rootxbin xdaemonxadm xlp x由于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分隔每行,如果想指定以某个字符作为分隔符来处理记录:
123456789101112131415161718# 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}'iqobject 48619#udp # iqobjectmatahari 49000#tcp # Matahari Broker 3)NF
NF是打印字段个数。
1234567891011121314# echo "a b c d e f" |awk '{print NF}'6打印最后一个字段:# echo "a b c d e f" |awk '{print $NF}'f打印倒数第二个字段:# echo "a b c d e f" |awk '{print $(NF-1)}'e排除最后两个字段:# echo "a b c d e f" |awk '{$NF="";$(NF-1)="";print$0}'a b cd排除第一个字段:# echo "a b c d e f" |awk '{$1="";print $0}' bc d e f 4)NR和FNR
NR统计记录编号,每处理一行记录,编号就会+1,FNR不同的是在统计第二个文件时会重新计数。
123456789101112131415161718192021打印行数:# tail -n5 /etc/services |awk '{print NR,$0}'1 com-bardac-dw 48556/tcp # com-bardac-dw2 com-bardac-dw 48556/udp # com-bardac-dw3 iqobject 48619/tcp # iqobject4 iqobject 48619/udp # iqobject5 matahari 49000/tcp # Matahari Broker打印总行数:# tail -n5 /etc/services |awk 'END{print NR}'5打印第三行:# tail -n5 /etc/services |awk 'NR==3' iqobject 48619/tcp # iqobject打印第三行第二个字段:# tail -n5 /etc/services |awk 'NR==3{print $2}'48619/tcp打印前三行:# tail -n5 /etc/services |awk 'NR"$0}ARGIND==2{print "b->"$0}'ab a->aa->ba->cb->cb->db->e 7)ENVIRON
ENVIRON调用系统变量。
12345678# awk 'BEGIN{print ENVIRON["HOME"]}'/root如果是设置的环境变量,还需要用export导入到系统变量才可以调用:# awk'BEGIN{print ENVIRON["a"]}'# export a# awk 'BEGIN{print ENVIRON["a"]}'123 8)FILENAME
FILENAME是当前处理文件的文件名。
1234567891011# awk 'FNR==NR{print FILENAME"->"$0}FNR!=NR{printFILENAME"->"$0}' a b a->aa->ba->cb->cb->db->e9)忽略大小写# echo "A a b c" |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。字符运算,未定义变量初始值为空。
举例测试:
123456# awk 'BEGIN{n=0;if(n)print"true";else print "false"}'false# awk'BEGIN{s="";if(s)print "true";else print"false"}'false# awk'BEGIN{if(s)print "true";else print "false"}'false 示例:
1)截取整数
12345678# echo "123abc abc123 123abc123"|xargs -n1 | awk '{print +$0}'1230123# echo "123abc abc123 123abc123"|xargs -n1 | awk '{print -$0}'-1230-123 2)感叹号
123456789101112131415打印奇数行:# seq 6 |awk 'i=!i'135读取第一行,i是未定义变量,也就是i=!0,!取反意思。感叹号右边是个布尔值,0或空字符串为假,非0或非空字符串为真,!0就是真,因此i=1,条件为真打印当前记录。没有print为什么会打印呢?因为模式后面没有动作,默认会打印整条记录。读取第二行,因为上次i的值由0变成了1,此时就是i=!1,条件为假不打印。读取第三行,上次条件又为假,i恢复初始值0,取反,继续打印。以此类推...可以看出,运算时并没有判断行内容,而是利用布尔值真假判断输出当前行。打印偶数行:# seq 6 |awk '!(i=!i)' 246 2)不匹配某行
12345678# tail /etc/services |awk '!/blp5/{print$0}'3gpp-cbsp 48049/tcp # 3GPPCell Broadcast Service isnetserv 48128/tcp # Image Systems NetworkServicesisnetserv 48128/udp # ImageSystems Network Servicescom-bardac-dw 48556/tcp # com-bardac-dwcom-bardac-dw 48556/udp # com-bardac-dwiqobject 48619/tcp # iqobjectiqobject 48619/udp # iqobjectmatahari 49000/tcp # MatahariBroker 3)乘法和除法
123456789101112131415161718192021# seq 5 |awk '{print $0*2}'246810# seq 5 |awk '{print $0%2}'10101打印偶数行:# seq 5 |awk '$0%2==0{print $0}'24打印奇数行:# seq 5 |awk '$0%2!=0{print $0}'135 4)管道符使用
123456# seq 5 |shuf |awk '{print$0|"sort"}'12345 5)正则表达式匹配
123456789101112131415161718# seq 5 |awk '$0~3{print $0}'3# seq 5 |awk '$0!~3{print $0}'1245# seq 5 |awk '$0~//{print $0}'34# seq 5 |awk '$0!~//{print $0}'125# seq 5 |awk '$0~/[^34]/{print $0}'125 6)判断数组成员
12# awk'BEGIN{a["a"]=123}END{if("a" in a)print "yes"}'FNR{if($0 ina)print $0}' a b345# awk 'FNR==NR{a[$0]=1;next}(a[$0]==1)' a b# a[$0]是通过b文件每行获取值,如果是1说明有# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}' a b345方法2:# awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' ab345方法3:# awk 'ARGIND==1{a[$0]=1}ARGIND==2 &&a[$0]==1' a b 345找出b文件在a文件不同记录:方法1:# awk 'FNR==NR{a[$0];next}!($0 in a)' ab 67# awk 'FNR==NR{a[$0]=1;next}(a[$0]!=1)' a b# awk'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}' a b67方法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' a b 3)合并两个文件
将a文件合并到b文件:
12345678910111213141516# cat azhangsan 20lisi 23wangwu 29# cat bzhangsan manlisi womanwangwu man# awk 'FNR==NR{a[$1]=$0;next}{printa[$1],$2}' a bzhangsan 20 manlisi 23 womanwangwu 29 man# awk 'FNR==NR{a[$1]=$0}NR>FNR{printa[$1],$2}' a b zhangsan 20 manlisi 23 womanwangwu 29 man 将a文件相同IP的服务名合并:
123456789101112# cat a192.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(v in a)printv,a}' a 192.168.1.4: httpd192.168.1.1: httpd tomcat192.168.1.2: httpd postfix192.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=httpdtomcat。每次遇到相同的下标(第一个字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。
4)将第一列合并到一行
12345678# cat file1 2 34 5 67 8 9# awk '{for(i=1;imax)max=$3}END{for(v in a)if(a==max)print v}'ag h 3e f 3 7)去除第一行和最后一行
1234# seq 5 |awk'NR>2{print s}{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和端口:
12345678910111213# cat aupstreamexample-servers1 { server 127.0.0.1:80 weight=1 max_fails=2fail_timeout=30s;}upstreamexample-servers2 { server 127.0.0.1:80 weight=1 max_fails=2fail_timeout=30s; server 127.0.0.1:82 backup;}# awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' a 127.0.0.1:80# awk '/example-servers1/,/}/{if(i>1)print s;s=$2;i++}' a# awk '/example-servers1/,/}/{if(i>1){print s}{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++作为计数器。
页:
[1]