一、awk简介
awk的强大之处在于能生成强大的格式化报告。
数据可以来自标准输入、一个或多个文件,或其它命令的输出。
它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。
它在命令行中使用,但更多是作为脚本来使用。
awk的处理文本和数据的方式是这样的,它逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。
如果没有指定处理动作,则把匹配的行显示到标准输出(屏幕),如果没有指定模式,则所有被操作所指定的行都被处理。
awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。下面介绍的awk是以GUN的gawk为例的,在linux系统中已把awk链接到gawk
二、awk使用
1、awk的命令格式
Usage: awk [options] -f 'progfile' file ...
Usage: awk [options] 'program' file ...
options:
-F : 指明输入时用到的字段分隔符
-v var=value :自定义变量
program:
PATTERN{ACTION STATEMENTS}
语句之间用分号分隔
2、操作和模式
- awk脚本(program)是由模式和操作组成的:
pattern {action}
如: awk ‘/root/{print $1}’ test
如 : awk ‘$3<100’ test
两者是可选的:
如果没有模式,则action应用到全部记录
如果没有action,则输出匹配全部记录。
默认情况下,每一个输入行都是一条记录,但用户可通过RS变量指定不同的分隔符进行分隔
- 模式
模式可以是以下任意一个:
/正则表达式/:
使用通配符的扩展集
如,/string/ ,表示匹配以string为字符串的行
关系表达式:
可以用下面运算符表中的关系运算符进行操作,可以是字符串或数字的比较
如$2>$1选择第二个字段比第一个字段长的行
模式匹配表达式:
用运算符~(匹配)和~!(不匹配)
模式,模式:
指定一个行的范围。该语法不能包括BEGIN和END模式
/1/,/10/ 从1到10行
'NR==1,NR==10' ,从1到10行
BEGIN:
让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量
END:
让用户在最后一条输入记录被读取之后发生的动作
- 操作
操作由一人或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内。
主要有四部份:
变量或数组赋值
输出命令
内置函数
控制流命令
3、awk脚本基本结构
# 一个awk脚本通常由:
# BEGIN语句块
# 能够使用模式匹配的通用语句块
# END语句块3部分组成,这三个部分是可选的。
awk 'BEGIN{ print "start" } pattern{ commands } END{ print "end" }' file
任意一个部分都可以不出现在脚本中,脚本通常是被单引号或双引号中,例如:
awk 'BEGIN{ i=0 } { i++ } END{ print i }' filename
awk "BEGIN{ i=0 } { i++ } END{ print i }" filename
4、awk的工作原理
awk 'BEGIN{ commands } pattern{ commands } END{ commands }'
-
第一步:执行
BEGIN{ commands }
语句块中的语句; -
第二步:从文件或标准输入(stdin)读取一行,然后执行
pattern{ commands }
语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕 -
第三步:当读至输入流末尾时,执行
END{ commands }
语句块。
BEGIN语句块:
在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中。
END语句块:
在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块。
pattern语句块:
是通用命令是最重要的部分,它也是可选的。
如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块。
示例
[ec2-user@aws ~]$ echo -e "A line 1nA line 2" | awk 'BEGIN{ print "Start" } { print } END{ print "End" }'
Start
A line 1nA line 2
End
当使用不带参数的print
时,它就打印当前行,当print
的参数是以逗号进行分隔时,打印时则以空格作为定界符。
在awk的print语句块中双引号是被当作拼接符使用,例如:
[ec2-user@aws ~]$ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; print var1,var2,var3; }'
v1 v2 v3
双引号拼接使用:
echo | awk '{ var1="v1"; var2="v2"; var3="v3"; print var1"="var2"="var3; }'
v1=v2=v3
{ }类似一个循环体,会对文件中的每一行进行迭代,通常变量初始化语句(如:i=0)以及打印文件头部的语句放入BEGIN语句块中,将打印的结果等语句放在END语句块中
5、常用使用示例
- 在脚本中使用多个命令
# 使用多个命令,则在每个命令之间加分号
# 冒号进行分割字段,然后把第一个字段替换为“Description:”,最后打印出整行数据
[ec2-user@aws ~]$ awk -F: '{$1="Description:"; print $0}' file
Description: There is a big dog and a little dog in the park
Description: There is a big cat and a little cat in the park
Description: There is a big tiger and a litle tiger in the park
- 从文件中读程序命令
# 如果program程序命令过多,可以单独放在一个文件中,然后从文件中读命令。
# 还是以上面为例,把
{
$1="Description:"
print $0
}
# 单独放在一个文件script1中。再用awk处理脚本时,需要用选项 -f 指定脚本程序的位置
[ec2-user@aws ~]$ awk -F: -f script1 file
Description: There is a big dog and a little dog in the park
Description: There is a big cat and a little cat in the park
Description: There is a big tiger and a litle tiger in the park
- 在处理数据之前运行脚本
# awk默认每次读入一行数据,然后用脚本进行处理。
# 如果想在处理文本之前预处理一些命令,可以用BEGIN关键字指定
[ec2-user@aws ~]$ awk -F: 'BEGIN{print "开始处理..."}{print $2}' file
开始处理...
There is a big dog and a little dog in the park
There is a big cat and a little cat in the park
There is a big tiger and a litle tiger in the park
- 在处理数据后运行脚本
# 用END关键字在处理完所有数据后,再运行善后处理工作
[ec2-user@aws ~]$ awk -F: '{print $2} END{print "处理结束..."}' file
There is a big dog and a little dog in the park
There is a big cat and a little cat in the park
There is a big tiger and a litle tiger in the park
处理结束...
三、在program中使用变量
变量又分为两种形式:awk内置的变量;用户自定义的变量。
1、内置变量
记录分隔符相关变量
- FS :输入字段分隔符
- OFS:输出字段分隔符
- RS:输入记录分割符
- ORS:输出字段分隔符
- FIELDWIDTHS:定义数据字段的宽度
数据分割段相关变量
FILENAME: 当前文件名
NR: 表示所有处理文件已处理的输入记录个数
FNR: 文件的当前记录数
NF: 表示数据文件中数据字段的个数,可以通过$NF获取最后一个数据字段
ARGC: 命令行参数个数
ARGV: 命令行参数数组
$0: 这个变量包含执行过程中当前行的文本内容。
$1: 这个变量包含第一个字段的文本内容。
$2: 这个变量包含第二个字段的文本内容。
示例
- 操作的file文件内容
The dog:There is a big dog and a little dog in the park
The cat:There is a big cat and a little cat in the park
The tiger:There is a big tiger and a litle tiger in the park
- FS用法
# 用FS指定字段分隔符为“:”,然后用“:”把每行数据分割为两段。
[ec2-user@aws ~]$ awk 'BEGIN{FS=":"} {print $1, $2}' file
The dog There is a big dog and a little dog in the park
The cat There is a big cat and a little cat in the park
The tiger There is a big tiger and a litle tiger in the park
OFS用法
# 用FS指定输入字段分隔符“:”后,每行数据分为两个数据段
# 输出时,用OFS指定两个数据字段用“>”拼接。
[ec2-user@aws ~]$ awk 'BEGIN{FS=":"; OFS=">"} {print $1, $2}' file
The dog>There is a big dog and a little dog in the park
The cat>There is a big cat and a little cat in the park
The tiger>There is a big tiger and a litle tiger in the park
RS和ORS用法
# 默认情况下RS和ORS设置为“\n”
# 表示输入数据流中的每一行作为一条记录,输出时每条记录之间也以“\n”进行分割。
下面以file2文件为例,fiel2文件中内容如下:
Tom is a student
and he is 20 years old
Bob is a teacher
and he is 40 years old
# 默认情况下,每行作为一条记录处理
# 但此种情况下,要把第一行和第二行作为一条记录处理,第三行和第四行作为一条记录处理
[ec2-user@aws ~]$ awk 'BEGIN{RS=""; ORS="\n"; FS="and"; OFS=","} {print $1, $2}' file1 file2
Tom is a student
, he is 20 years old
Bob is a teacher
, he is 40 years old
ARGC和ARGV
# ARGC表示命令行中的参数个数,ARGV是参数数组
# 可见,每处理一行数据时,都是两个参数,第一个是awk本身,第二个是处理的文件名
awk -F: '{print ARGC, ARGV[0], ARGV[1]}' file
2 awk file
2 awk file
2 awk file
NR和FNR
# FNR表示处理文件的当前记录号,NR表示所有处理文件已处理的输入记录个数
[ec2-user@aws ~]$ awk -F: '{print "NR="NR, "FNR="FNR, $2}' file file1
NR=1 FNR=1 There is a big dog and a little dog in the park
NR=2 FNR=2 There is a big cat and a little cat in the park
NR=3 FNR=3 There is a big tiger and a litle tiger in the park
NR=4 FNR=1
NR=5 FNR=2
# 注意,不要对NR和FNR加$
# 例如,如果对NR加$,加入NR等于5,实际就是取每条记录的第5个数据字段,实际没有这么多,只能取到空
2、用户自定义变量
在脚本中使用用户自定义变量
建立一个script1的脚本,内容如下:
awk '
BEGIN{
FS = ":"
name = "lzj>"
}
{
$1 = name
print $0
}' file
运行该脚本 ./script
[ec2-user@aws ~]$ ./script
lzj> There is a big dog and a little dog in the park
lzj> There is a big cat and a little cat in the park
lzj> There is a big tiger and a litle tiger in the park
# 在脚本中直接定义了一个name变量,在脚本程序中可以直接引用该变量。
# 注意在yaml命令中,赋值语句“=”的前后是不能有空格的
# 但是在awk程序的内部是可以有的,因为awk是一种单独的编程语言。
在命令行中使用变量
首先定义一个script2脚本,其内容为:
BEGIN{
FS = ":"
}
{
print $n
}
脚本中有一个n的变量,在命令行中传入
[ec2-user@aws ~]$ awk -f script2 n=2 file
There is a big dog and a little dog in the park
There is a big cat and a little cat in the park
There is a big tiger and a litle tiger in the park
注意一个问题:在命令行中传入的参数,默认在BEGIN是不能获取的,例如
script2脚本改为如下,在BEGIN部分获取n的值:
BEGIN{
FS = ":"
print "请输出第二部分...", n
}
{
print $n
}
[ec2-user@aws ~]$ awk -f script2 n=2 file
请输出第二部分...
There is a big dog and a little dog in the park
There is a big cat and a little cat in the park
There is a big tiger and a litle tiger in the park
# 运行命令,发现BEGIN部分的n值并没有打印出来。
# 但是如果用-v选项指定,并且把变量放在脚本代码之前,在BEGIN部分就可以访问了
[ec2-user@aws ~]$ awk -v n=2 -f script2 file
请输出第二部分... 2
There is a big dog and a little dog in the park
There is a big cat and a little cat in the park
There is a big tiger and a litle tiger in the park
四、数组应用
数组是awk的灵魂,处理文本中最不能少的就是它的数组处理。
因为数组索引(下标)可以是数字和字符串在awk中数组叫做关联数组(associative arrays)。
awk 中的数组不必提前声明,也不必声明大小。
数组元素用0或空字符串来初始化,这根据上下文而定。
1、数组的定义
- 数字做数组索引(下标):
Array[1]="sun"
Array[2]="kai"
# 使用print Array[1]会打印出sun
# 使用print Array[2]会打印出kai
- 字符串做数组索引(下标):
Array["first"]="www"
Array["last"]="name"
Array["birth"]="1987"
# 使用print["birth"]会得到1987。
- 读取数组的值
# 输出的顺序是随机的
{ for(item in array) {print array[item]}; }
# Len是数组的长度
{ for(i=1;i<=len;i++) {print array[i]}; }
2、数组相关函数
得到数组长度:
# length返回字符串以及数组长度
# split进行分割字符串为数组,也会返回分割得到数组长度。
awk 'BEGIN{
#定义个字符串变量
info="it is a test";
#split(字符,数组,分隔符)
lens=split(info,tA," ");
print length(tA),lens;
}'
# 输出结果
4 4
输出数组内容(无序,有序输出):
awk 'BEGIN{
info="it is a test";
split(info,tA," ");
for(k in tA)
{
print k,tA[k];
}
}'
# 输出结果
4 test
1 it
2 is
3 a
for…in
输出,因为数组是关联数组,默认是无序的。所以通过
for…in
得到是无序的数组。如果需要得到有序数组,需要通过下标获得。
# 注意:数组下标是从1开始,与C数组不一样。
awk 'BEGIN{
info="it is a test";
tlen=split(info,tA," ");
for(k=1;k<=tlen;k++)
{
print k,tA[k];
}
}'
# 输出结果
1 it
2 is
3 a
4 test
3、二维、多维数组使用
awk的多维数组在本质上是一维数组,更确切一点,awk在存储上并不支持多维数组。
awk提供了逻辑上模拟二维数组的访问方式。
类似一维数组的成员测试,多维数组可以使用
if ( (i,j) in array)
这样的语法,但是下标必须放置在圆括号中。类似一维数组的循环访问,多维数组使用
for ( item in array )
这样的语法遍历数组。与一维数组不同的是,多维数组必须使用
split()
函数来访问单独的下标分量。
awk 'BEGIN{
for(i=1;i<=9;i++){
for(j=1;j<=i;j++){
tarr[i,j]=i*j; print i,"*",j,"=",tarr[i,j];
}
}
}'
# 输出结果
1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
1 * 4 = 4
1 * 5 = 5
1 * 6 = 6
...
9 * 6 = 54
9 * 7 = 63
9 * 8 = 72
9 * 9 = 81
五、使用正则匹配模式
在脚本中用正则匹配数据行时,正则表达式一定要放在脚本命令的左大括号之前,例如
# 匹配所有数据行中带dog字符的
[ec2-user@aws ~]$ awk 'BEGIN{FS = ":"} /dog/{print $2}' file
There is a big dog and a little dog in the park
1、用~匹配特定数据字段
用正则表达式匹配指定的数据字段,匹配成功的,就是脚本要处理的数据
[ec2-user@aws ~]$ awk -F: '$1 ~ /^The cat$/{print $2}' file
There is a big cat and a little cat in the park
数据行中的第一个数据字段满足以The开头,cat结尾的数据行,执行后面的脚本。示例可知只有一条数据行满足记录。
# 当然也可以通过匹配的否定形式(!~)来排除数据行
[ec2-user@aws ~]$ awk -F: '$1 !~ /dog/{print $2}' file
There is a big cat and a little cat in the park
There is a big tiger and a litle tiger in the park
2、使用数学表达式
可以在匹配数据行时用数学表达式。
以处理file3文件为例,文件内容为:
1:This is 1 line
2:This is 2 line
3:This is 3 line
4:This is 4 line
匹配第一个数据字段除2余0的数据行记录
[ec2-user@aws ~]$ awk -F: '$1 % 2 == 0{print $2}' file3
This is 2 line
This is 4 line
# 数学表达式中的+、-、*、/、^(平方)等都可以应用。
# 另外数学表达式不仅可以用在匹配部分
# 还可以用在BEGIN、program命令脚本、END部分。
# 注意,如果要比较文本的话,只能用“==”进行比较
[ec2-user@aws ~]$ awk -F: '$1 == "The cat"{print $2}' file
There is a big cat and a little cat in the park
六、流程控制语句
在linux awk的while、do-while和for语句中允许使用break,continue语句来控制流程走向,也允许使用exit这样的语句来退出。
break中断当前正在执行的循环并跳到循环外执行下一条语句。
awk中,流程控制语句,语法结构,与c语言类型。
有了这些语句,其实很多yaml程序都可以交给awk,而且性能是非常快的。下面是各个语句用法。
1、条件判断语句
格式为:
if(表达式)
语句1
else
语句2
# 格式中语句1可以是多个语句
# 为了方便判断和阅读,最好将多个语句用{}括起来
# awk分枝结构允许嵌套,其格式为:
if(表达式)
{语句1}
else if(表达式)
{语句2}
else
{语句3}
示例:
awk 'BEGIN{
test=100;
if(test>90){
print "very good";
}
else if(test>60){
print "good";
}
else{
print "no pass";
}
}'
very good
# 每条命令语句后面可以用;分号结尾。
2、while循环
格式为:
while(表达式)
{语句}
示例:
awk 'BEGIN{
test=100;
total=0;
while(i<=test){
total+=i;
i++;
}
print total;
}'
5050
3、for循环
for循环有两种格式:
- 格式1:
for(变量 in 数组)
{语句}
示例: 统计字段出现次数
# 编辑文件 test
www.baidu.com
123
424
4523
www.bai.com
123
www.baidu.com
# 统计重复行次数
awk '{
# 创建一个数组,将字段作为下标,值为自增,第一个值为1
# Count[www.baidu.com]=1
Count[$NF]++}
END{
# 创建一个变量,来遍历数组下标
for(arr in Count)
# 打印 数组下标,和数组对于值,即为下标次数
print arr,Count[arr]
}' test
www.bai.com 1
www.baidu.com 2
123 2
424 1
4523 1
- 格式2:
for(变量;条件;表达式)
{语句}
示例:
awk 'BEGIN{
total=0;
for(i=0;i<=100;i++){
total+=i;
}
print total;
}'
5050
4、其他语句
break :
当 break 语句用于 while 或 for 语句时,导致退出程序循环。
continue:
当 continue 语句用于 while 或 for 语句时,使程序循环移动到下一个迭代。
next:
能能够导致读入下一个输入行,并返回到脚本的顶部。
这可以避免对当前输入行执行其他的操作过程。
exit:
语句使主输入循环退出并将控制转移到END,如果END存在的话。
如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行。
七、用户自定义函数
用户自定义函数一定要放在调用之前进行定义
格式为:
function function_name([variable])
{
statement1
statement2
....
}
例如处理file4文件,内容如下,只提取其中姓名和电话
Bob NanjingRoad 6623432
Terry BeijingRoad 6689764
Lily GuangzRoad 6623678
新建脚本script7,内容如下
awk '
function fshow()
{
printf "%-5s : %s\n", $1, $3
}
BEGIN{
print "开始处理..."
}
{
fshow()
}' file4
运行脚本如下
[ec2-user@aws ~]$ ./script7
开始处理...
Bob : 6623432
Terry : 6689764
Lily : 6623678
# 注意:
# printf的用法通C语言一样;
# 在脚本中BEGIN后面一定要紧跟“{”,否则会出错