AWK是一种处理文本文件的语言,是一个强大的文本分析工具。awk其实是一门编程语言,支持判断、数组、循环等功能。
AWK5种模式:
(1)空模式
(2)关系运算模式
(3)正则模式
(4)行范围模式
(5)BEGIN/END模式

语法:

  1. awk [选项参数] 'script' var=value file(s)
  2. awk [选项参数] -f scriptfile var=value file(s)
  3. awk [options] 'pattern{action}' file

用法一:

awk [options] ‘pattern{action}’ file action指的是动作,awk最常用的动作就是print 和 printf 。

1、不指定pattern,使用最简单的action。

awk '{print}' test.log

# awk是逐行处理的,默认以换行符为标记识别每一行。新的一行开始,awk以用户指定的分隔符处理新的一行。
# 没有指定默认空格作为分隔符。每个字段按照顺序,分别对应到awk的内置变量中。分割完后的第一个字段为$1,第二个字段为$2..... ,类推。$0表示当前处理的整个一行。$NF表示当前行分割后的最后一列。
# 注意:$NF和NF不一样,$NF表示分割后的最后一列,NF表示当前行被分割后一共有几列。
# 1、输出第4 5列,$5表示将当前行按照分隔符分割后的第五列。默认空格作为分隔符,awk自动将连续的空格作为一个分隔符。
df | awk '{print $4,$5}'    
# 2、输出第2 4 5列
df | awk '{print $2,$4,$5}'
# 3、awk添加字段,或者将自己的字段与文件中的列相结合 
awk '{print $1, $2, "string"}' test.log                                          # abc 123 string
awk '{print "diyilie:"$1, "dierlie:"$2}' test.log                        # diyilie:abc dierlie:123
awk '{print "diyilie:"$1, "666", "dierlie:"$2}' test.log        # diyilie:abc 666 dierlie:123

2、使用模式pattern。

介绍两种特殊模式,BEGIN END。BGEIN模式指定处理文本之前需要执行的操作,END模式表示处理完所有行之后执行的操作。

# 处理文本之前输出【year month date time level content】,处理结束输出【....】  输出有表头有结束的格式
awk 'BEGIN{print "year month date time level content"}{print $6,$2,$3,$4,$7,$8,$9}END{print "..."}' ts.log


awk分隔符:

awk默认以空格作为分隔符,但是这样描述是不准确的。
awk分为输入分隔符输出分隔符两种。
输入分隔符:FS,默认空白字符(即空格)awk默认以空白字符对每一行进行分割。
输出分隔符:OFS,awk将每行分割后,输出到屏幕上以什么字符作为分割。默认的输出分隔符也是空格。
多个分隔符利用[]然后在里面写分隔符即可

# 输入分隔符:使用 -F 选项,指定符号作为分隔符。还可以通过FS这个内置变量来指定输入分隔符
awk -F# '{print $1, $3}' test.log        # 指定#号作为输入分隔符

# 输出分隔符:用awk的内置变量OFS设置输出分隔符,使用变量的时候要配合 -v 选项
awk -F# -v OFS="---" '{print $1, $2}' test.log
awk -v FS="#" -v OFS="---" '{print $1, $2}' test.log

# 在输出的时候,如果想将两列合并到一起。即不使用分隔符。
awk '{print $1$2}' test.log

# 多分隔符
awk -F "[/]" 'NR == 4 {print $0,"\n",$1}' /etc/passwd

awk变量:

awk变量分为内置变量自定义变量
awk常用的内置变量:
FS : 输入字段分隔符,默认空白符
OFS : 输出字段分隔符,默认空白符
RS : 输入记录分隔符(输入换行符),指定输入时的换行符
ORS : 输出记录分隔符(输出换行符),输出时用指定符号代替换行符
NF : number of filed 当前行的字段个数(即当前行被分割成几列)
NR :行号,当前处理文本行的行号
FNR : 各文件分别计数的行号
FILENAME :当前文件名
AGRC :命令行参数的个数
ARGV : 数组,保存命令行所给定的各参数

awk '{print NR,NF}' ts.log    # 输出行号和列数(被分割为多少列)

# 自定义变量
awk -v myVar="testVar" 'BEGIN{print myVar}'

# 自定义变量:引用shell变量
abc=6666666
awk -v myVar=$abc 'BEGIN{print myVar}'

# 在程序中定义即可。变量定义和动作之间要用分号隔开,还可以定义多个变量
awk 'BEGIN{myvar="ttt"; print myvar}'
awk 'BEGIN{myvar1="aaa"; myvar2="bbb"; print myvar1, myvar2}'

awk格式化能力

awk本身负责文本切割,printf负责文本格式化
print动作只能进行简单的文本输出功能。如果需要改变文本的格式,就需要printf命令
在awk中使用printf动作时,需要注意以下几点
(1)使用printf动作输出的文本不会换行,如果需要换行,可以在对应的格式替换符后边加 \n 进行转义
(2)使用printf动作时,指定的格式与被格式化的文本之间,需要用逗号隔开
(3)使用printf动作时,格式中的格式替换符必须与被格式的文本一一对应

awk '{print $1}' test
Wed Sep
Wed Sep
awk '{printf $1,$2}' ts.log
WedWedWedWedWed%        # printf输出的字符不会换行

# 格式化输出,输出1、2列,中间使用逗号隔开,使用\n换行
awk '{printf "%s,%s\n", $1,$2}' ts.log
Wed,Sep
Wed,Sep
awk '{printf "第一列:%s,第二列:%s\n", $1,$2}' ts.log
第一列:Wed,第二列:Sep
第一列:Wed,第二列:Sep

awk 'BEGIN{print "星期\t月份"}{printf "第一列:%s,第二列:%s\n", $1,$2}' ts.log
星期    月份
第一列:Wed,第二列:Sep
awk 'BEGIN{print "星期\t月份"}{printf "%s\t%s\n", $1,$2}' ts.log
星期    月份
Wed     Sep
Wed     Sep

关系表达式模式活着关系运算符模式

用到了关系运算符的模式称之为:关系表达式模式活着关系运算符模式

关系运算符 含义 用法示例
< 小于 x < y
<= 小于等于 x <= y
== 等于 x == y
!= 不等于 x != y
>= 大于等于 x >= y
> 大于 x > y
~ 与对应正则匹配则为真 x ~/正则/
!~ 与对应正则不匹配则为真 x !~/正则/
awk 'NF>2 {print $0}' test1                # 输出第二行之后的所有数据
awk 'NF<=4 {print $0}' test1            # 输出1-4行
awk '$1=="aaa" {print $0}' test1     # 输出第一列值为aaa的行

正则模式

正则包含在//中。表达式中包含/需要转义
(1)在awk命令中使用正则模式时,使用到的正则用法属于”扩展正则表达式”。
(2)当使用{x,y}这种次数匹配的正则表达式时,需要配合—posix或者—re-interval选项。(centos7貌似目前没有这个注意事项了)

# 输出本行中包含JMXUtils字符的行
awk '/JMXUtils/{print}' ts.log 
Wed Sep 16 19:29:14 CST 2020 [INFO] [JMXUtils]set druid.filters:stat
awk '/JMXUtils/{print $8,$9}' ts.log

行范围模式

正则模式:awk ‘/正则/ {动作}’ /some/file
行范围模式:awk ‘/正则1/,/正则2/ {动作}’ /some/file

# 匹配行中符合/JMXUtils/,/success./两个正则的行
awk '/JMXUtils/,/success./{print}' ts.log
# 打印第X行到第Y行之间的所有行.不包含边界值
awk 'NR>3 && NR<6 {print NR,$0}' ts.log
4 Wed Sep 16 19:29:14 CST 2020 [INFO] [JMXUtils]reg druid mbean(com.alibaba.druid.stat.JdbcStatManager) success.
5 Wed Sep 16 19:29:14 CST 2020 [INFO] [JMXUtils]reg mbeans success.

# 关系运算符~ 需要和正则配合。   输出第2列匹配192.168.{1-3位数字}.{1-3位数字}规则的行
awk '$2~/192.168\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1,$2}' ak.log

awk动作解析

1、if语句、if…else、if(){ } else if(){ }else{ }

# 两个命令等同.每个{}是一组命令,多个命令之间可以使用分号拼接组合
awk '{print $1} {print $2}' test5  
awk '{print $1; print $2}' test5

# if语句: { if(条件){todo..} }
# 如果行号是1,则输出;如果todo只有一个命令,则可以省略{}
awk '{ if(NR==1){ print NR,$0} }' ak.log     # 省略模式:awk '{ if(NR==1) print $0 }' ak.log

# 如果行号是1,则以\t作为输出分隔符,输出:NR,$1,$2,$3
awk '{ if(NR==1){printf "%s\t%s\t%s\t%s", NR,$1,$2,$3} }' ak.log
1       主机A   192.168.2.123   192.168.1.124

# 如行号是1,输出:NR,$1,$2。组合命令,使用分号分割
awk '{ if(NR==1){print NR; print $1; print $2} }' ak.log
1
主机A
192.168.2.123

# 以:分割文件,判断$1小于1000的输出为系统用户,$1>1000的输出为普通用户
 awk -v FS=':' '{ if($3<1000){ printf "%s\t%s\t%s\n", "系统用户",$1,$3 } else { printf "%s\t%s\t%s\n", "普通用户",$1,$3 } }' /etc/passwd

2、for循环和while循环。
1、用break和continue跳出循环。
2、exit命令退出awk命令。在AWK中使用了END模式后,exit的作用并不是退出awk命令,而是直接执行END模式中的动作
3、next命令可以使awk不对当前行执行对应的动作。而是直接处理下一行
#for循环语法格式1
for(初始化; 布尔表达式; 更新) {
//代码语句
}

#for循环语法格式2
for(变量 in 数组) {
//代码语句
}

#while循环语法
while( 布尔表达式 ) {
//代码语句
}

#do…while循环语法
do {
//代码语句
}while(条件)
**

awk数组详解

在awk中,数组的下标是从1开始的。也可以从0开始设置下标;awk中数组的下标不仅可以是数字,还可以是任意字符串
命令太长使用Linux命令行的换行符进行换行,Linux的命令换行符为反斜线 \ 。
在awk中将元素的值设置为空是合法的
使用delete可以删除数组中的元素,也可以删除整个数组

# 定义数组,并输出下标1的值.支持赋值为"".支持混合类型的key
awk 'BEGIN{ animals[0]="cat"; animals[1]="dog"; animals[2]="monkey"; animals[3]="snake"; animals[4]=""; animals["unkown"]="unkown"; print animals[1] }'

# 判断元素是否存在;空格不是空
if(huluwa[4]==""){print "第5个元素不存在"}
# 语法 if(下标 in 数组名) 。从而判断数组中是否存在对应的元素。 也可以是否否定符,if(!(5 in animals))
if(5 in huluwa){print "第六个元素存在"}
awk 'BEGIN{ animals[0]="cat"; animals[1]="dog"; animals[2]="monkey"; if(5 in animals){ print "第6个元素存在" }else{ print "第6个元素不存在" } }'
第6个元素不存在

# 删除单个元素:delete animals[1]; 删除数组:delete animals;
awk 'BEGIN{ animals[0]="cat"; animals[1]="dog"; animals[2]="monkey"; animals[3]="snake"; delete animals[1]; delete animals; print animals[1] }'

# 遍历输出数组
# 第一种for循环: for(i=0;i<len;i++)
awk 'BEGIN{ animals[0]="cat"; animals[1]="dog"; animals[2]="monkey"; animals[3]="snake"; for(i=0;i<4;i++){ print i,animals[i] } }'
0 cat
1 dog
2 monkey
3 snake
awk 'BEGIN{ animals[0]="cat"; animals[1]="dog"; animals[2]="monkey"; animals[3]="snake"; for(i=0;i<4;i++){ printf "%s\t", animals[i] } }'
cat     dog     monkey  snake 

# 第二种for循环:for(x in arr)  PS:当key非连续数字,或者是混合索引类型时,使用for in遍历. 使用for in循环会转换index为字符串,所以输出是无序的
awk 'BEGIN{ animals[0]="cat"; animals[1]="dog"; animals[2]="monkey"; animals[3]="snake"; for(i in animals){ printf "%s\t%s\n", i,animals[i] } }'
2       monkey
3       snake
0       cat
1       dog

split函数可以将文本中指定的字段分割,将分割后的字段自动复制到数组中,通过split函数生成的数组下标默认是从1开始的

实际应用:

# 如果字符串参与运算,字符串将被当作数字0进行运算。空字符串在参与运算时,也会被当做数字0。
# 引用一个不存在的元素时。元素被赋值为一个空字符串,当对这个元素进行自加运算,这个元素就被当成0
# 统计第3列的值的和。
awk 'BEGIN{su = 0} {su += $3; print su}' ts.log
16
32
48
64
80
96
awk 'BEGIN{ sum = 0 } { sum += $3 } END{ print sum }' ts.log

# 使用了一个空模式,一个END模式。在空模式中,创建了一个数组,并使用数组作为元素的下标。当执行第一行时,我们引用的ips("192.168.1.1")。
#    很明显这个元素并不存在,所以当执行了自加运算后,ips("192.168.1.1")被赋值为1。同理,执行第二行。
# 直到再次遇到相同的IP地址时,使用同一个IP地址的元素会再加1。因此,我们统计出每个IP出现的次数。
awk '{ ips[$1]++ } END{ for(i in ips){ print i, ips[i] } }' test8        # 推荐写法
awk '{ ips[$3]; ips[$3]++ } END{ for(i in ips){ print i,ips[i] } }' ak.log 
awk 'BEGIN{ ips[$3] } { ips[$3]++ } END{ for(i in ips){ print i,ips[i] } }' ak.log
192.168.1.124 3
192.168.1.60 3
172.16.100.3 3
172.16.100.5 3
172.16.100.6 3
172.16.100.7 3
172.16.200.2 3

# 统计每个主机出现次数
awk '{ machines[$1]++ } END{ for(i in machines){ printf "%s: %s\n",i,machines[i] } }' ak.log
主机A: 3
主机B: 3
主机C: 3
主机D: 3
主机E: 4
主机F: 4
主机G: 4
# 统计每个名字出现的次数
awk '{ for(s = 1; s <= NF; s++){ machines[$1]++ } } END{ for(i in machines){ printf "%s: %s\n",i,machines[i] } }' names
Lucas: 2
Tyler: 2
Green: 2
Allen: 2
Kevin: 1
William: 4
Angel: 2

awk内置函数

awk的内置函数大致可以分为算数函数,字符串函数,时间函数,其他函数等
split函数,将指定字符串按照指定的分隔符切割。可以通过split函数动态生成数组,而不用手动设置数组中每个元素的值。split函数本身也有返回值,其返回值就是分割以后的数组长度
asort函数根据元素的值进行排序。但是经过asort函数排序过后数组的下标会被重置

(1)算数函数
最常用的算数函数有rand函数,srand函数,int函数。可以使用rand函数生成随机数,使用rand函数时,需要配合srand函数。否则rand函数返回的值将一成不变

# 输出随机数,但是值固定不变。值是小数
awk 'BEGIN{print rand()}'
# 配置srand,输出大于0的随机数。值是小数
awk 'BEGIN{ srand(); print rand() }'
# 输出整数随机数:随机小数*100,使用int函数取整
awk 'BEGIN{ srand(); print int(rand() * 100) }'

(2)字符串函数
使用gsub函数或者sub函数替换某些文本。想把一个文本中的小写字母l都换成大写字母L。则可以使用gsub函数。gsub函数会在指定范围内查找指定的字符,并将其替换为指定字符串
可以根据正则表达式,替换字符串
gsub是指定范围内的全局替换,sub是指定范围内的单次替换
length函数,获取指定字符串的长度
index函数,获取指定字符位于整个字符串中的位置

# 消息字符e替换为E。省略第三个参数,省略第三个参数时默认是$0,替换全部
awk '{ gsub("e", "E", $0); print $0 }' names
awk '{ gsub("e", "E"); print $0 }' names 

# 使用正则,替换ee为00
awk '{ gsub("[ee]", "00"); print $0 }' names

# sub与gsub的区别
awk '{sub("l","L",$1);print $0}' test9        # 每行有多个符合条件的替换时,只替换第一次配置的值
awk '{gsub("l","L",$1);print $0}' test9        # 每行有多个符合条件的替换时,替换全部

# length函数:已空格作为输入分隔符,输出每一行的长度
awk '{ for(i = 1; i <= NF; i++) { printf "%s\t%s\n", $i,length($i) } }' names
William 7
Aiden   5
James   5
# 输出整列的字符串长度
awk '{ printf "%s\t%s\n", $0,length($0) }' names
awk '{ printf "%s\t%s\n", $0,length() }' names        # length()的入参可缺省,默认为$0
Allen Phillips  14
Green Lee       9

# 输出指定字符串在每行的索引位置
awk '{ print index($0, "Lee") }' names
0
7
21

# 使用split分割字符串为数组.下标从1开始
awk -v ts="大娃:二娃:三娃" 'BEGIN{ split(ts, boys, ":"); for (b in boys){ print b,boys[b] } }'
2 二娃
3 三娃
1 大娃
# split的返回值是分割以后的数组长度
awk -v ts="大娃:二娃:三娃" 'BEGIN{ print split(ts, boys, ":") }'
3


awk之三元运算和打印奇偶行

awk -v FS=":" '{usertype=$3<1000?"系统用户":"普通用户"; print $1, usertype}' /etc/passwd 
awk -v FS=":" '{usertype=$3<1000? a++:b++} END{print a, b}' /etc/passwd

# 打印奇偶行
awk '{ odd = (NR % 2 == 0) ? "偶数行:"NR : "奇数行:"NR } { print odd } ' ak.log
awk '{ print (NR % 2 == 0) ? "偶数行:"NR : "奇数行:"NR }' ak.log