AWK详解

一、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后面一定要紧跟“{”,否则会出错

参考文档

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值