awk入门-基础篇
5年前
810
0
本篇博客主要介绍linux常用命令中的对文本和数据进行处理的命令awk的用法。
介绍awk
grep 、sed、awk被称为linux中的”三剑客”。
grep 更适合单纯的查找或匹配文本
sed 更适合编辑匹配到的文本
awk 更适合格式化文本,对文本进行较复杂格式处理
咱们先学习一下 awk
awk是由Alfred Aho 、Peter Weinberger 和 Brian Kernighan这三个人创造的,awk由这个三个人的姓氏的首个字母组成。
awk是一个报告生成器,它拥有强大的文本格式化的能力,这就是专业的说法。
awk其实是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。
awk基础
awk基本语法如下,看不懂没关系,我们会慢慢举例。
awk [options] ‘program’ file1 , file2 , ```
对于上述语法中的program来说,又可以细分成pattern和action,也就是说,awk的基本语法如下
awk [options] 'Pattern{Action}' file
options表示的是选项,下面使用过-F选项和-v选项。
Action指的就是动作,awk擅长文本格式化,并且将格式化以后的文本输出,所以awk最常用的动作就是print和printf,因为awk要把格式化完成后的文本输出啊,所以,这两个动作最常用。
Pattern表示模式,在下文中我们将详细的介绍一下awk中的模式。
简单示例
我们先从最简单用法开始了解awk,我们先不使用[options] ,也不指定pattern,直接使用最简单的action,从而开始认识awk,示例如下:
[www.itselfstudy.cn]# echo aaa >> test
[www.itselfstudy.cn]# awk '{print}' test
aaa
现在,我们来操作一下另一个类似的场景。
[www.itselfstudy.cn]# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/vda1 41151808 17467432 21570944 45% /
devtmpfs 931592 0 931592 0% /dev
tmpfs 941860 0 941860 0% /dev/shm
tmpfs 941860 352 941508 1% /run
tmpfs 941860 0 941860 0% /sys/fs/cgroup
tmpfs 188376 0 188376 0% /run/user/0
[www.itselfstudy.cn]# df | awk '{print $5}'
Use%
45%
0%
0%
1%
0%
0%
上图中的示例没有使用到options和pattern,上图中的awk ‘{print $5}’,表示输出df的信息的第5列,$5表示将当前行按照分隔符分割后的第5列.
awk是逐行处理的,逐行处理的意思就是说,当awk处理一个文本时,会一行一行进行处理,处理完当前行,再处理下一行,awk默认以”换行符”为标记,识别每一行,awk会按照用户指定的分割符去分割当前行,如果没有指定分割符,默认使用空格作为分隔符。
$0 表示显示整行 ,$NF表示当前行分割后的最后一列($0和$NF均为内置变量)
注意,$NF 和 NF 要表达的意思是不一样的,对于awk来说,$NF表示最后一个字段,NF表示当前行被分隔符切开以后,一共有几个字段。
也就是说,假如一行文本被空格分成了7段,那么NF的值就是7,$NF的值就是$7, 而$7表示当前行的第7个字段,也就是最后一列,那么每行的倒数第二列可以写为$(NF-1)。
我们也可以一次输出多列,使用逗号隔开要输出的多个列,如下,一次性输出第一列和第二列
[www.itselfstudy.cn]# cat test
aaa bbb ccc ddd
eee fff ggg hhh
[www.itselfstudy.cn]# awk '{print $1,$2}' test
aaa bbb
eee fff
我们还能够添加自己的字段,将自己的字段与文件中的列结合起来,如下做法,都是可以的。
[www.itselfstudy.cn]# awk '{print $1,$2,"string"}' test
aaa bbb string
eee fff string
[www.itselfstudy.cn]# awk '{print "no1:", $1,"no2:", $2}' test
no1: aaa no2: bbb
no1: eee no2: fff
但是要注意,$1这种内置变量的外侧不能加入双引号,否则$1会被当做文本输出
模式(pattern)
“模式”这个词听上去文绉绉的,不是特别容易理解,那么我们换一种说法,我们把”模式”换成”条件”,可能更容易理解,那么”条件”是什么意思呢?我们知道,awk是逐行处理文本的,也就是说,awk会先处理完当前行,再处理下一行,如果我们不指定任何”条件”,awk会一行一行的处理文本中的每一行,如果我们指定了”条件”,只有满足”条件”的行才会被处理,不满足”条件”的行就不会被处理。这样说是不是比刚才好理解一点了呢?
再啰嗦一遍,当awk进行逐行处理的时候,会把pattern(模式)作为条件,判断将要被处理的行是否满足条件,是否能跟”模式”进行匹配,如果匹配,则处理,如果不匹配,则不进行处理。
关系运算符模式
如下图所示,test2文件中有3行文本,第一行有4列,第二行有2列,第三行只有3列。而下图的awk命令中,就使用到了一个简单的模式。
[www.itselfstudy.cn]# cat test2
aaa bbb ccc ddd
eee fff
ggg hhh iii
[www.itselfstudy.cn]# awk 'NF==3 {print $0}' test2
ggg hhh iii
满足条件,就会执行相应的动作,而上例中使用到的运算符都是常见的关系运算符,那么awk都支持哪些关系运算符呢?我们来总结一下:
关系运算符 | 含义 | 用法示例 |
---|---|---|
< | 小于 | x < y |
<= | 小于等于 | x <= y |
== | 等于 | x == y |
!= | 不等于 | x != y |
>= | 大于等于 | x >= y |
> | 大于 | x > y |
~ | 与对应的正则匹配则为真 | x ~ /正则/ |
!~ | 与对应的正则不匹配则为真 | x !~ /正则/ |
特殊模式:BEGIN/END
再说一下awk中最特殊的模式
AWK 包含两种特殊的模式:BEGIN 和 END。
BEGIN 模式指定了处理文本之前需要执行的操作:
END 模式指定了处理完所有行之后所需要执行的操作:
[www.itselfstudy.cn]# awk 'BEGIN{print "111","222"} {print $0} END{print "333","444"}' test
111 222
aaa bbb ccc ddd
eee fff ggg hhh
333 444
正则模式
正则模式可以理解为,把”正则表达式”当做”条件”,能与正则匹配的行,就算满足条件,满足条件的行才会执行对应的动作,不能被正则匹配到的行,则不会执行对应的动作。如果你觉得我说的不明白,来看一个小例子,就能理解
我们知道,在Linux中,/etc/passwd文件中存放了用户信息,那么假设 ,我们想要从/etc/passwd文件中找出”用户名以gs开头”的用户
我们可以使用grep命令,配合正则表达式,找出对应的信息:
[www.itselfstudy.cn]# grep "^gs" /etc/passwd
gs:x:500:500:gs:/home/gs:/bin/bash
gsaaa:x:501:667::/home/gsaaa:/bin/bash
gsbbb:x:502:502::/home/gsbbb:/bin/bash
使用awk命令,怎样从/etc/passwd文件中找出用户名以zsy开头的用户,示例如下:
[www.itselfstudy.cn]# awk '/^gs/{print $0}' /etc/passwd
gs:x:500:500:gs:/home/gs:/bin/bash
gsaaa:x:501:667::/home/gsaaa:/bin/bash
gsbbb:x:502:502::/home/gsbbb:/bin/bash
不管是使用grep命令,还是使用awk命令,都使用了相同的正则表达式”^zsy”
唯一的区别就是,在grep命令中,直接使用了正则表达式,而在awk命令中,正则表达式被放入了两个斜线中。
[www.itselfstudy.cn]# awk -F ":" 'BEGIN{printf "%-10s%-10s\n","用户名","用户ID"} /^gs/{printf "%-10s\t%-10s\n",$1,$3}' /etc/passwd
用户名 用户ID
gs 500
gsaaa 501
gsbbb 502
上例使用了BEGIN模式,并且格式化输出了一行文本作为”表头”,上例中也使用了正则模式,并且格式化输出了/etc/passwd文件中的第一列与第三列(用户名字段与用户ID字段),上例中,只使用了awk一条命令就完成了如下多项工作。
- 从/etc/passwd文件中找出符合条件的行(用户名以zsy开头的用户)。
- 找出符合条件的文本行以后,以”:”作为分隔符,将文本行分段。
- 取出我们需要的字段,格式化输出。
- 结合BEGIN模式,输出一个格式化以后的文本,提高可读性。
我们知道,/etc/passwd中保存了用户信息,其中每行的最后一个字段为用户使用的登录shell,假设,我们想要从passwd文件中找出使用/bin/bash作为登录shell的用户:
[www.itselfstudy.cn]# grep "/bin/bash$" /etc/passwd
root:x:0:0:root:/root:/bin/bash
mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/bash
gs:x:500:500:gs:/home/gs:/bin/bash
gsaaa:x:501:667::/home/gsaaa:/bin/bash
gsbbb:x:502:502::/home/gsbbb:/bin/bash
ccc:x:777:777:ccc:/home/ccc:/bin/bash
ddd:x:778:888::/home/ddd:/bin/bash
行范围模式
在介绍行范围模式之前,先来思考一个小问题,有一个文本文件,文件内容如下:
[www.itselfstudy.cn]# cat -n test4
1 Allen Plillips
2 Green Lee
3 William Aiden James Lee
4 Angek Jack
5 Ttler Kevin
6 Lucas Thomas
7 Kevin
Lee这个名字出现了两次,第一次出现是在第2行,Kevin这个名字也出现了两次,第一次出现是在第5行。
假设我想从上述文本中找出,从Lee第一次出现的行,到Kevin第一次出现的行之间的所有行,我该怎么办呢?
[www.itselfstudy.cn]# awk '/Lee/,/Kevin/{print $0}' test4
Green Lee
William Aiden James Lee
Angek Jack
Ttler Kevin
把上述行范围模式的语法与正则模式的语法对比着理解,可能更加方便我们理解。
awk ‘/正则表达式/{动作}’ /some/file
awk ‘/正则1/,/正则2/{动作}’ /some/file
上图中第二种语法是行范围模式的语法,它表示,从被正则1匹配到的行开始,到被正则2匹配到的行结束,之间的所有行都会执行对应的动作,所以,这种模式被称为行范围模式,因为它对应的是一个范围以内的所有行,但是需要注意的是,在行范围模式中,不管是正则1,还是正则2,都以第一次匹配到的行为准,就像上述示例中,即使Lee在第2行与第3行中都出现了,但是由于正则1先匹配到第2行中的lee,所以,最终打印出的内容从第2行开始,即使Kevin在第5行与第7行中都出现了,但是由于Kevin第一次出现在第5行,所以最终打印出的内容到第5行结束,也就是说,最终打印出了第2行到第5行以内的所有行。
动作(Action)
输出语句
下面代码为例:
[www.itselfstudy.cn]# awk '{print $0}' test
aaa bbb ccc ddd
eee fff ggg hhh
我们将动作拆分成了两个部分。最外侧的括号,即”{ }”。第二部分:”print $0”
其实,这两个部分都可以被称之为”动作”,只不过它们是不同”类型”的动作而已。
“print”属于”输出语句”类型的动作,顾名思义,”输出语句”类型的动作的作用就是输出、打印信息,没错,”print”与”printf”都属于”输出语句”类型的动作。
“{ }”其实也可以被称之为”动作”,只不过,”{ }”属于”组合语句”类型的动作,顾名思义,”组合语句”类型的动作的作用就是将多个代码组合成代码块。
这样说可能不容易理解,我们来看个小示例,就容易理解了,示例如下。
[www.itselfstudy.cn]# cat test5
1 a
2 b
3 c
[www.itselfstudy.cn]# awk '{print $1} {print $2}' test5
1
a
2
b
3
c
上面示例,使用了两个大括号”{ }”,它们属于”组合语句”类型的动,它们分别将两个print括住,表示这两个print动作分别作为两个独立的个体
我们可以这样理解,上图中一共有4个”动作”,两对大括号,两个print,但是上图中,每个大括号中只有一个动作
组合语句
“组合语句”的作用是将多个代码或多个动作组合成代码块,组合后的代码块被当做一个整体,那么,我们能不能把上图中的两个print动作组合成一个整体呢?
[www.itselfstudy.cn]# awk '{print $1; print $2}' test5
1
a
2
b
3
c
当我们把多个动作(多段代码)组合成一个代码块的时候,每段动作(每段代码)之间需要用分号”;”隔开。
除了print这种”输出语句”能够被称之为动作以外,像”{ }”这种”组合语句”也能被称之为动作,只不过它们的类型不同,功能也不同。
那么,除了”输出语句”与”组合语句”以外,还有其他种类的动作吗?
必须的,我们现在就来认识另一种动作,它就是”控制语句”。
控制语句
if(条件){
语句1;语句2;…
}
else if(条件2){
语句1;语句2;…
}
else{
语句1;语句2;…
}
[www.itselfstudy.cn]# cat test5
1 a
2 b
3 c
[www.itselfstudy.cn]# awk '{ if(NR==1){print $0} }' test5
1 a
还记得我们在前文中使用到的”模式”吗?示例如下
[www.itselfstudy.cn]# awk 'NR==1 {print $0}' test5
1 a
没错,上图中的用法为awk的”模式”的用法,而我们今天所介绍的用法为awk的”动作”的用法,虽然两者在语法上有所区别,但是达到的目的相同的。
for循环语法格式1
for(初始化; 布尔表达式; 更新) {
//代码语句
}
for循环语法格式2
for(变量 in 数组) {
//代码语句
}
while循环语法
while( 布尔表达式 ) {
//代码语句
}
do…while循环语法
do {
//代码语句
}while(条件)
[www.itselfstudy.cn]# awk 'BEGIN{ for(i=1;i<=6;i++){print i} }'
1
2
3
4
5
6
[www.itselfstudy.cn]# awk -v i=1 'BEGIN{ while(i<5){print i;i++} }'
1
2
3
4
[www.itselfstudy.cn]# awk 'BEGIN{i=1; while(i<5){print i;i++} }'
1
2
3
4
[www.itselfstudy.cn]# awk 'BEGIN{i=1; do{print i;i++}while(i<5) }'
1
2
3
4
分隔符(options)
输入分隔符,英文原文为field separator,此处简称为FS
输入分割符,默认是空白字符(即空格),awk默认以空白字符为分隔符对每一行进行分割。
输出分割符,英文原文为output field separator,此处简称为OFS
awk将每行分割后,输出在屏幕上的时候,以什么字符作为分隔符,awk默认的输出分割符也是空格。
输入分隔符
输入分隔符比较容易理解,当awk逐行处理文本的时候,以输入分隔符为准,将文本切成多个片段,默认使用空格,但是,如果一段文字中没有空格,我们可以指定以特定的文字或符号作为输入分割符,比如下面的例子,我们指定使用”#”作为输入分隔符。
[www.itselfstudy.cn]# cat test1
aaa#bbb#ccc#ddd
eee#fff#ggg#hhh
[www.itselfstudy.cn]# awk -F# '{print $1,$2}' test1
aaa bbb
eee fff
我们使用了-F 选项,指定了使用#号作为输入分隔符,除此之外还能够通过设置内部变量的方式,指定awk的输入分隔符,awk内置变量FS可以用于指定输入分隔符,但是在使用变量时,需要使用-v选项,用于指定对应的变量,比如 -v FS=’#’,如下:
[www.itselfstudy.cn]# awk -v FS='#' '{print $1,$2}' test1
aaa bbb
eee fff
输出分隔符
输出分割符的意思就是:当我们要对处理完的文本进行输出的时候,以什么文本或符号作为分隔符。
当awk为我们输出每一列的时候,会使用空格隔开每一列,其实,这个空格,就是awk的默认的输出分隔符
我们可以使用awk的内置变量OFS来设定awk的输出分隔符,示例如下
[www.itselfstudy.cn]# awk '{print $1,$2}' test
aaa bbb
eee fff
[www.itselfstudy.cn]# awk -v OFS="+++" '{print $1,$2}' test
aaa+++bbb
eee+++fff
同时指定输入分隔符和输出分割符了,示例如下:
[www.itselfstudy.cn]# cat test1
aaa#bbb#ccc#ddd
eee#fff#ggg#hhh
[www.itselfstudy.cn]# awk -v FS="#" -v OFS="+++" '{print $1,$2}' test1
aaa+++bbb
eee+++fff
如果,在输出的时候,我们想要让两列合并在一起显示,不使用输出分隔符分开显示,该怎么做呢?
看看下面的代码,你会发现一个有”逗号”,一个没有”逗号”。
[www.itselfstudy.cn]# awk '{print $1,$2}' test
aaa bbb
eee fff
[www.itselfstudy.cn]# awk '{print $1 $2}' test
aaabbb
eeefff
[www.itselfstudy.cn]# awk '{print $1$2}' test
aaabbb
eeefff
awk变量
对于awk来说”变量”又分为”内置变量” 和 “自定义变量” , “输入分隔符FS”和”输出分隔符OFS”都属于内置变量。
内置变量
awk常用的内置变量以及其作用如下:
- FS:输入字段分隔符, 默认为空白字符
- OFS:输出字段分隔符, 默认为空白字符
- RS:输入记录分隔符(输入换行符), 指定输入时的换行符
- ORS:输出记录分隔符(输出换行符),输出时用指定符号代替换行符
- NF:number of Field,当前行的字段的个数(即当前行被分割成了几列),字段数量
- NR:行号,当前处理的文本行的行号。
- FNR:各文件分别计数的行号
- FILENAME:当前文件名
- ARGC:命令行参数的个数
- ARGV:数组,保存的是命令行所给定的各参数
NR
内置变量NR表示每一行的行号,内置变量NF表示每一行中一共有几列,那么,也就是说,我们可以通过下例中的方法,得到test1文本中,每一行的行号以及每一行对应的列的数量。
[www.itselfstudy.cn]# cat test
aaa bbb ccc ddd
eee fff ggg hhh
[www.itselfstudy.cn]# awk '{print "行号:" NR,$0,"每行个数:" NF}' test
行号:1 aaa bbb ccc ddd 每行个数:4
行号:2 eee fff ggg hhh 每行个数:4
FNR
当我们使用awk同时处理多个文件,并且使用NR显示行号的时候,效果如下:
[www.itselfstudy.cn]# cat test
aaa bbb ccc ddd
eee fff ggg hhh
[www.itselfstudy.cn]# cat test1
aaa#bbb#ccc#ddd
eee#fff#ggg#hhh
[www.itselfstudy.cn]# awk '{print NR,$0}' test test1
1 aaa bbb ccc ddd
2 eee fff ggg hhh
3 aaa#bbb#ccc#ddd
4 eee#fff#ggg#hhh
从返回结果可以看出,awk处理多个文件的时候,如果使用NR显示行号,那么,多个文件的所有行会按照顺序进行排序。
可是,如果我们想要分别显示两个文件的行号,该怎么办呢,这个时候就会用到内置变量FNR,效果如下。
[www.itselfstudy.cn]# awk '{print FNR,$0}' test test1
1 aaa bbb ccc ddd
2 eee fff ggg hhh
1 aaa#bbb#ccc#ddd
2 eee#fff#ggg#hhh
RS
RS是输入行分隔符,如果不指定,默认的”行分隔符”就是我们所理解的”回车换行”。
假设,我们不想以默认的”回车换行”作为”行分隔符”,而是想使用空格作为所谓的行分隔符,也就是说,我们想让awk认为,每遇到一个空格,就换行,那么我们该怎么做呢?示例如下:
[www.itselfstudy.cn]# awk '{print NR,$0}' test
1 aaa bbb ccc ddd
2 eee fff ggg hhh
[www.itselfstudy.cn]# awk -v RS=" " '{print NR,$0}' test
1 aaa
2 bbb
3 ccc
4 ddd
eee
5 fff
6 ggg
7 hhh
ORS
默认情况下,awk将”回车换行”,当做”输出行分隔符”,
现在,设置”+++”为输出行分隔符,示例如下:
[www.itselfstudy.cn]# awk '{print NR,$0}' test
1 aaa bbb ccc ddd
2 eee fff ggg hhh
[www.itselfstudy.cn]# awk -v ORS="+++" '{print NR,$0}' test
1 aaa bbb ccc ddd+++2 eee fff ggg hhh+++[www.itselfstudy.cn]#
把刚才学到的”输入换行符”和”输出换行符”同时使用,看看是什么效果,示例如下:
[www.itselfstudy.cn]# awk '{print NR,$0}' test
1 aaa bbb ccc ddd
2 eee fff ggg hhh
[www.itselfstudy.cn]# awk -v RS=" " -v ORS="+++" '{print NR,$0}' test
1 aaa+++2 bbb+++3 ccc+++4 ddd
eee+++5 fff+++6 ggg+++7 hhh
+++[www.itselfstudy.cn]#
FILENAME
FILENAME这个内置变量,从字面上,就能看出是什么意思,没错,就是显示文件名,演示效果如下:
[www.itselfstudy.cn]# awk '{print FILENAME,FNR,$0}' test test1
test 1 aaa bbb ccc ddd
test 2 eee fff ggg hhh
test1 1 aaa#bbb#ccc#ddd
test1 2 eee#fff#ggg#hhh
ARGC与ARGV
ARGV内置变量表示的是一个数组,这个数组中保存的是命令行所给定的参数。这样解释还是很模糊,不容易理解,我们来看看示例:
[www.itselfstudy.cn]# awk 'BEGIN{print "aaa"}' test test1
aaa
[www.itselfstudy.cn]# awk 'BEGIN{print "aaa",ARGV[1]}' test test1
aaa test
[www.itselfstudy.cn]# awk 'BEGIN{print "aaa",ARGV[1],ARGV[2]}' test test1
aaa test test1
我们先使用BEGIN模式,输出一个字符串”aaa”,然后,传入两个文件的文件名作为参数,我们发现,BEGIN模式正常执行了打印操作,输出了”aaa”字符串 ,我们使用同样的命令,同样使用BEGIN模式,只不过,这次不只打印”aaa”,还打印ARGV这个数组中的第二个元素的值。
ARGV内置变量表示的是一个数组,既然是数组,就需要用上图中的下标的方式,引用对应元素的值,因为数组的索引都是从0开始的,所以,ARGV[1]表示引用ARGV数组中的第二个元素的值,从返回结果可以看出,ARGV[1]对应的值为test,同理,我们又使用第三条命令,多打印了一个ARGV[2]的值,发现ARGV[2]对应的值为test1,ARGV内置变量表示的是:所有参数组成的数组。那么细心的你一定会问了,ARGV[0]对应的是哪个参数呢,我们来打印一下:
[www.itselfstudy.cn]# awk 'BEGIN{print "aaa",ARGV[0],ARGV[1],ARGV[2]}' test test1
aaa awk test test1
awk就是这么规定的,’pattern{ action }’并不被看做是参数,awk被看做为参数。
说明了ARGV变量以后,再说ARGC变量的作用,就容易多了。
在刚才的例子中,应该有三个参数,awk、test1、test2,这三个参数作为数组的元素存放于ARGV中,现在,而ARGC则表示参数的数量,也可以理解为ARGV数组的长度。示例如下
[www.itselfstudy.cn]# awk 'BEGIN{print "aaa",ARGV[0],ARGV[1],ARGV[2],ARGC}' test test1
aaa awk test test1 3
自定义变量
自定义变量,顾名思义,就是用户定义的变量,有两种方法可以自定义变量。
方法一:-v varname=value
变量名区分字符大小写。
[www.itselfstudy.cn]# awk -v var="test" 'BEGIN{print var}'
test
方法二:在program中直接定义。
直接在program中定义即可,但是注意,变量定义与动作之间需要用分号”;”隔开。
[www.itselfstudy.cn]# awk 'BEGIN{var="test";print var}'
test
当我们需要在awk中引用shell中的变量的时候,则可以通过方法一间接的引用。举例如下:
[www.itselfstudy.cn]# tmp=test
[www.itselfstudy.cn]# awk -v var=$tmp 'BEGIN{print var}'
test
数组
如果你有过任何一种编程语言的使用经验,那么你一定知道,我们可以通过数组的下标(或者称索引),引用数组中的元素,其他语言中,数组的下标通常由0开始,也就是说,如果想要引用数组中的第1个元素,则需要引用对应的下标”[0]”,awk中的数组也是通过引用下标的方法,获取数组中的元素的,但是在awk中,数组元素的下标默认从1开始,但是为了兼容你的使用习惯,我们也可以从0开始设置下标,此处不用纠结,到后面自然会明白,我们先来看一个最简单的示例。
[www.itselfstudy.cn]# awk 'BEGIN{ arr[0]="老大";arr[1]="老二";arr[2]="老三";print arr[1]}'
老二
当一个元素不存在于数组时,如果我们直接引用这个不存在的元素,awk会自动创建这个元素,并且默认为这个元素赋值为”空字符串”
delete可以删除数组中的元素
[www.itselfstudy.cn]# awk 'BEGIN{ arr["first"]="老大";arr["second"]="老二";arr["third"]="老三";delete arr["first"]; print arr["first"]}'
[www.itselfstudy.cn]#
应用实例
当引用了一个不存在的元素时,元素被赋值为空字符串,当对这个元素进行自加运算时,元素的值就变成了1,因为,空字符串在参与运算时,被当做0使用了,所以,综上所述,我们对一个不存在的元素进行自加运算后,这个元素的值就变成了自加运算的次数,自加x次,元素的值就被赋值为x,自加y次,元素的值就被赋值为y,示例如下
[www.itselfstudy.cn]# awk 'BEGIN{print arr["ip"]; arr["ip"]++; arr["ip"]++; print arr["ip"]}'
2
[www.itselfstudy.cn]
利用这一点,我们就可以统计文本中某些字符出现的次数,比如IP地址,示例如下:
[www.itselfstudy.cn]# cat test6
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.11
192.168.1.3
192.168.1.2
192.168.1.2
[www.itselfstudy.cn]# awk '{ count[$1]++ } END{ for(i in count){print i,"数量:"count[i]} }' test6
192.168.1.11 数量:1
192.168.1.1 数量:1
192.168.1.2 数量:3
192.168.1.3 数量:2
我们对一个不存在的元素进行自加运算后,这个元素的值就变成了自加运算的次数
如果你以后再想统计文本中某类文本出现的”次数”,就可以使用上述套路了,活学活用以后,你会发现上述套路特别好使。
比如,如果我们想要统计如下文本中每个人名出现的次数,我们则可以使用如下命令。
[www.itselfstudy.cn]# cat test4
Allen Plillips
Green Lee
William Aiden James Lee
Angek Jack
Ttler Kevin
Lucas Thomas
Kevin
[www.itselfstudy.cn]# awk '{ for(i=1;i<=NF;i++){ count[$i]++ } } END{ for(j in count){print j,"数量:"count[j]} }' test4
James 数量:1
Lucas 数量:1
William 数量:1
Thomas 数量:1
Green 数量:1
Jack 数量:1
Kevin 数量:2
Lee 数量:2
Allen 数量:1
Plillips 数量:1
Ttler 数量:1
Angek 数量:1
Aiden 数量:1
内置函数
awk的内置函数大致可以分类为算数函数、字符串函数、时间函数、其他函数等,此处只总结一下个人觉得常用的函数。
算术函数
格式 | 描述 |
---|---|
atan2( y, x ) | 返回 y/x 的反正切。 |
cos( x ) | 返回 x 的余弦;x 是弧度。 |
sin( x ) | 返回 x 的正弦;x 是弧度。 |
exp( x ) | 返回 x 幂函数。 |
log( x ) | 返回 x 的自然对数。 |
sqrt( x ) | 返回 x 平方根。 |
int( x ) | 返回 x 的截断至整数的值。 |
rand( ) | 返回任意数字 n,其中 0 <= n < 1。 |
srand( [expr] ) | 将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。 |
最常用的算数函数有rand函数、srand函数、int函数
使用rand函数生成随机数,但是使用rand函数时,需要配合srand函数,否则rand函数返回的值将一直不变,示例如下:
[www.itselfstudy.cn]# awk 'BEGIN{print rand()}'
0.237788
[www.itselfstudy.cn]# awk 'BEGIN{print rand()}'
0.237788
[www.itselfstudy.cn]# awk 'BEGIN{print rand()}'
0.237788
可以看到,如果单纯的使用rand函数,生成的值是不变的,可以配合srand函数,生成一个大于0小于1的随机数
[www.itselfstudy.cn]# awk 'BEGIN{srand();print rand()}'
0.684434
[www.itselfstudy.cn]# awk 'BEGIN{srand();print rand()}'
0.371791
[www.itselfstudy.cn]# awk 'BEGIN{srand();print rand()}'
0.517114
字符串函数
格式 | 描述 |
---|---|
gsub( Ere, Repl, [ In ] ) | 除了正则表达式所有具体值被替代这点,它和 sub 函数完全一样地执行。 |
sub( Ere, Repl, [ In ] ) | 用 Repl 参数指定的字符串替换 In 参数指定的字符串中的由 Ere 参数指定的扩展正则表达式的第一个具体值。sub 函数返回替换的数量。出现在 Repl 参数指定的字符串中的 &(和符号)由 In 参数指定的与 Ere 参数的指定的扩展正则表达式匹配的字符串替换。如果未指定 In 参数,缺省值是整个记录($0 记录变量)。 |
index( String1, String2 ) | 在由 String1 参数指定的字符串(其中有出现 String2 指定的参数)中,返回位置,从 1 开始编号。如果 String2 参数不在 String1 参数中出现,则返回 0(零)。 |
length [(String)] | 返回 String 参数指定的字符串的长度(字符形式)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。 |
blength [(String)] | 返回 String 参数指定的字符串的长度(以字节为单位)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。 |
substr( String, M, [ N ] ) | 返回具有 N 参数指定的字符数量子串。子串从 String 参数指定的字符串取得,其字符以 M 参数指定的位置开始。M 参数指定为将 String 参数中的第一个字符作为编号 1。如果未指定 N 参数,则子串的长度将是 M 参数指定的位置到 String 参数的末尾 的长度。 |
match( String, Ere ) | 在 String 参数指定的字符串(Ere 参数指定的扩展正则表达式出现在其中)中返回位置(字符形式),从 1 开始编号,或如果 Ere 参数不出现,则返回 0(零)。RSTART 特殊变量设置为返回值。RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为 -1(负一)。 |
tolower( String ) | 返回 String 参数指定的字符串,字符串中每个大写字符将更改为小写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。 |
toupper( String ) | 返回 String 参数指定的字符串,字符串中每个小写字符将更改为大写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。 |
sprintf(Format, Expr, Expr, . . . ) | 根据 Format 参数指定的 printf 子例程格式字符串来格式化 Expr 参数指定的表达式并返回最后 |
我们可以使用gsub函数或sub函数替换某些文本,先来 看看gsub函数怎样使用
[www.itselfstudy.cn]# cat test4
Allen Plillips
Green Lee
William Aiden James Lee
Kevin
[www.itselfstudy.cn]# awk '{gsub("l","L",$1); print $0}' test4
ALLen Plillips
Green Lee
WiLLiam Aiden James Lee
Kevin
看完上述示例,我想你应该已经明白了gsub函数的作用,没错,gsub函数会在指定范围内查找指定的字符,并将其替换为指定的字符串。
那么sub函数与gsub函数有什么不同呢?我们来对比一下。
[www.itselfstudy.cn]# awk '{gsub("l","L",$1); print $0}' test4
ALLen Plillips
Green Lee
WiLLiam Aiden James Lee
Kevin
[www.itselfstudy.cn]# awk '{sub("l","L",$1); print $0}' test4
ALlen Plillips
Green Lee
WiLliam Aiden James Lee
Kevin
当使用gsub函数时,gsub会替换指定范围内的所有符合条件的字符。
而使用sub函数则不同,当使用sub函数时,sub函数只会替换指定范围内第一次匹配到的符合条件的字符。
通过length函数,获取到指定字符串的长度,示例如下
[www.itselfstudy.cn]# cat test7
Allen Plillips
Green Lee
William Aiden James Lee
Kevin
[www.itselfstudy.cn]# awk '{ for(i=1;i<=NF;i++){print $i,length($i)} }' test7
Allen 5
Plillips 8
Green 5
Lee 3
William 7
Aiden 5
James 5
Lee 3
Kevin 5
我们输出了文本中每个单词的长度,其实,length函数可以省略传入的参数,即不指定任何字符换,当省略参数时,默认使用”$0”作为参数,示例如下。
[www.itselfstudy.cn]# awk '{ print $0,length() }' test7
Allen Plillips 14
Green Lee 9
William Aiden James Lee 23
Kevin 5
使用index函数,获取到指定字符位于整个字符串中的位置,示例如下:
[www.itselfstudy.cn]# cat test7
Allen Plillips
Green Lee
William Aiden James Lee
Kevin
[www.itselfstudy.cn]# awk '{ print index($0,"Lee") }' test7
0
7
21
0
我们使用index函数,在每一行中咋找字符串”Lee”,如果Lee存在于当前行,则返回字符串Lee位于当前行的位置,如果Lee不存在于当前行,则返回0,表示当前行并不存在Lee,如上图所示,第二行中包含Lee,而且Lee位于第二行的第7个字符的位置,所以返回数字7。
通过split函数,我们可以将指定的字符串按照指定的分割符切割,将切割后的每一段赋值到数组的元素中,从而动态的创建数组,示例如下。
[www.itselfstudy.cn]# awk -v arr="老大,老二,老三" 'BEGIN{ split(arr,list,",");for(i in list){print list[i]} }'
老大
老二
老三
我们通过split函数,将字符串arr切割了,以”,”作为分割符,将分割后的字符串保存到了名为list的数组中,当我们输出数组中的元素时,每个元素的值为分割后的字符,其实,split函数也有对应的返回值,其返回值就是分割以后的数组长度,示例如下:
[www.itselfstudy.cn]# awk -v arr="老大,老二,老三" 'BEGIN{ print split(arr,list,",") }'
3
时间函数
格式 | 描述 |
---|---|
mktime( YYYY MM dd HH MM ss[ DST]) | 生成时间格式 |
strftime([format [, timestamp]]) | 格式化时间输出,将时间戳转为时间字符串 具体格式,见下表. |
systime() | 得到时间戳,返回从1970年1月1日开始到当前时间(不计闰年)的整秒数 |