|
上节讲了grep、sed工具,已经能满足常见的文本处理需求,但有些需求对于他们来说心有余而力不足,今天所讲的工具就能完成他们大多数的功能,它就是三剑客中的老大AWK,我相信一定不会让你失望,下面一起看看吧!
本章大纲:
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=[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程序处理文件
# vi test.awk
{print$2}
# tail -n3 /etc/services |awk -f test.awk
48049/tcp
48128/tcp
49000/tcp
2)指定分隔符,打印指定字段
打印第二字段,默认以空格分隔:
# tail -n3 /etc/services |awk '{print $2}'
48049/tcp
48128/tcp
48128/udp
指定冒号为分隔符打印第一字段:
# awk-F ':' '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
......
还可以指定多个分隔符,作为同一个分隔符处理:
# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'
iqobject
iqobject
MatahariBroker
# tail -n3 /etc/services |awk -F'[/#]' '{print $1}'
iqobject 48619
iqobject 48619
matahari 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}'
48619
48619
49000
[]元字符的意思是符号其中任意一个字符,也就是说每遇到一个/或#时就分隔一个字段,当用多个分隔符时,就能更方面处理字段了。
3)变量赋值
# 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全局变量到文件
# seq 5|awk --dump-variables '{print $0}'
1
2
3
4
5
# cat awkvars.out
ARGC:number (1)
ARGIND:number (0)
ARGV:array, 1 elements
BINMODE: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}'
Service Port Description
===
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp #iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
END模式是在程序处理完才会执行。
例如:打印页尾
# tail /etc/services |awk '{print $0}END{print "===\nEND......"}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
===
END......
6)格式化输出awk命令到文件
# 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 Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast ServiceProtocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
===
END......
# cat awkprof.out
# gawk profile, created Sat Jan 7 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/正则匹配
匹配包含tcp的行:
# tail /etc/services |awk '/tcp/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
匹配开头是blp5的行:
# tail /etc/services |awk '/^blp5/{print $0}'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
匹配第一个字段是8个字符的行:
# tail /etc/services |awk '/^[a-z0-9]{8} /{print $0}'
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
8)逻辑and、or和not
匹配记录中包含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 Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 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)匹配范围
# tail /etc/services |awk '/^blp5/,/^com/'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-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[0]是awk
ENVIRON
当前系统的环境变量
FILENAME
输出当前处理的文件名
IGNORECASE
忽略大小写
SUBSEP
数组中下标的分隔符,默认为"\034"
示例:
1)FS和OFS
在程序开始前重新赋值FS变量,改变默认分隔符为冒号,与-F一样。
# awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5
rootx
bin x
daemonx
adm x
lp x
也可以使用-v来重新赋值这个变量:
# awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5 # 中间逗号被换成了OFS的默认值
rootx
bin x
daemonx
adm x
lp x
由于OFS默认以空格分隔,反向引用多个字段分隔的也是空格,如果想指定输出分隔符这样:
# awk 'BEGIN{FS=":";OFS=":"}{print $1,$2}' /etc/passwd |head -n5
root:x
bin:x
daemon:x
adm:x
lp:x
也可以通过字符串拼接实现分隔:
# awk 'BEGIN{FS=":"}{print $1"#"$2}' /etc/passwd |head -n5
root#x
bin#x
daemon#x
adm#x
lp#x
2)RS和ORS
RS默认是\n分隔每行,如果想指定以某个字符作为分隔符来处理记录:
# echo "www.baidu.com/user/test.html" |awk'BEGIN{RS="/"}{print $0}'
www.baidu.com
user
test.html
RS也支持正则,简单演示下:
# seq-f "str%02g" 10 |sed 'n;n;a\-----' |awk 'BEGIN{RS="-+"}{print$1}'
str01
str04
str07
str10
将输出的换行符替换为+号:
# 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 # iqobject
matahari 49000#tcp # Matahari Broker
3)NF
NF是打印字段个数。
# 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不同的是在统计第二个文件时会重新计数。
打印行数:
# tail -n5 /etc/services |awk '{print NR,$0}'
1 com-bardac-dw 48556/tcp # com-bardac-dw
2 com-bardac-dw 48556/udp # com-bardac-dw
3 iqobject 48619/tcp # iqobject
4 iqobject 48619/udp # iqobject
5 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}'a b
a->a
a->b
a->c
b->c
b->d
b->e
7)ENVIRON
ENVIRON调用系统变量。
# awk 'BEGIN{print ENVIRON["HOME"]}'
/root
如果是设置的环境变量,还需要用export导入到系统变量才可以调用:
# awk'BEGIN{print ENVIRON["a"]}'
# export a
# awk 'BEGIN{print ENVIRON["a"]}'
123
8)FILENAME
FILENAME是当前处理文件的文件名。
# awk 'FNR==NR{print FILENAME"->"$0}FNR!=NR{printFILENAME"->"$0}' a b
a->a
a->b
a->c
b->c
b->d
b->e
9)忽略大小写
# echo "A a b c" |xargs -n1 |awk 'BEGIN{IGNORECASE=1}/a/'
A
a
等于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";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)截取整数
# echo "123abc abc123 123abc123"|xargs -n1 | awk '{print +$0}'
123
0
123
# echo "123abc abc123 123abc123"|xargs -n1 | awk '{print -$0}'
-123
0
-123
2)感叹号
打印奇数行:
# seq 6 |awk 'i=!i'
1
3
5
读取第一行,i是未定义变量,也就是i=!0,!取反意思。感叹号右边是个布尔值,0或空字符串为假,非0或非空字符串为真,!0就是真,因此i=1,条件为真打印当前记录。
没有print为什么会打印呢?因为模式后面没有动作,默认会打印整条记录。
读取第二行,因为上次i的值由0变成了1,此时就是i=!1,条件为假不打印。
读取第三行,上次条件又为假,i恢复初始值0,取反,继续打印。以此类推...
可以看出,运算时并没有判断行内容,而是利用布尔值真假判断输出当前行。
打印偶数行:
# seq 6 |awk '!(i=!i)'
2
4
6
2)不匹配某行
# tail /etc/services |awk '!/blp5/{print$0}'
3gpp-cbsp 48049/tcp # 3GPPCell Broadcast Service isnetserv 48128/tcp # Image Systems NetworkServices
isnetserv 48128/udp # ImageSystems Network Services
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # MatahariBroker
3)乘法和除法
# seq 5 |awk '{print $0*2}'
2
4
6
8
10
# seq 5 |awk '{print $0%2}'
1
0
1
0
1
打印偶数行:
# seq 5 |awk '$0%2==0{print $0}'
2
4
打印奇数行:
# seq 5 |awk '$0%2!=0{print $0}'
1
3
5
4)管道符使用
# seq 5 |shuf |awk '{print$0|"sort"}'
1
2
3
4
5
5)正则表达式匹配
# seq 5 |awk '$0~3{print $0}'
3
# seq 5 |awk '$0!~3{print $0}'
1
2
4
5
# seq 5 |awk '$0~/[34]/{print $0}'
3
4
# seq 5 |awk '$0!~/[34]/{print $0}'
1
2
5
# seq 5 |awk '$0~/[^34]/{print $0}'
1
2
5
6)判断数组成员
# awk'BEGIN{a["a"]=123}END{if("a" in a)print "yes"}'FNR{if($0 ina)print $0}' a b
3
4
5
# 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 b
3
4
5
方法2:
# awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' ab
3
4
5
方法3:
# awk 'ARGIND==1{a[$0]=1}ARGIND==2 &&a[$0]==1' a b
3
4
5
找出b文件在a文件不同记录:
方法1:
# awk 'FNR==NR{a[$0];next}!($0 in a)' ab
6
7
# 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 b
6
7
方法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文件:
# cat a
zhangsan 20
lisi 23
wangwu 29
# cat b
zhangsan man
lisi woman
wangwu man
# awk 'FNR==NR{a[$1]=$0;next}{printa[$1],$2}' a b
zhangsan 20 man
lisi 23 woman
wangwu 29 man
# awk 'FNR==NR{a[$1]=$0}NR>FNR{printa[$1],$2}' a b
zhangsan 20 man
lisi 23 woman
wangwu 29 man
将a文件相同IP的服务名合并:
# cat a
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
# awk 'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1] $2}END{for(v in a)printv,a[v]}' a
192.168.1.4: httpd
192.168.1.1: httpd tomcat
192.168.1.2: httpd postfix
192.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)将第一列合并到一行
# cat file
1 2 3
4 5 6
7 8 9
# awk '{for(i=1;imax)max=$3}END{for(v in a)if(a[v]==max)print v}'a
g h 3
e f 3
7)去除第一行和最后一行
# seq 5 |awk'NR>2{print s}{s=$0}'
2
3
4
读取第一行,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和端口:
# cat a
upstreamexample-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++}}' a
127.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)
|
|
|