bobbai 发表于 2018-8-26 10:14:35

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&quot;true&quot;;else print &quot;false&quot;}'false# awk'BEGIN{s=&quot;&quot;;if(s)print &quot;true&quot;;else print&quot;false&quot;}'false# awk'BEGIN{if(s)print &quot;true&quot;;else print &quot;false&quot;}'false  示例:
  1)截取整数
12345678# echo &quot;123abc abc123 123abc123&quot;|xargs -n1 | awk '{print +$0}'1230123# echo &quot;123abc abc123 123abc123&quot;|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|&quot;sort&quot;}'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[&quot;a&quot;]=123}END{if(&quot;a&quot; in a)print &quot;yes&quot;}'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==&quot;a&quot;{a[$0]}FILENAME==&quot;b&quot;{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==&quot;a&quot;{a[$0]=1}FILENAME==&quot;b&quot; && 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=&quot;:&quot;;OFS=&quot;:&quot;}{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]
查看完整版本: Shell文本处理三剑客之awk