Shell(四):awk编程
1、awk简介
awk因三位缔造者的名字而命令(Aho、Weinberger和Kernighan),是一种能够对结构化数据进行操作,并产生格式化报表的编程语言。
awk功能与sed相似,都是用来进行文本处理的,awk语言可以从文件或字符串中基于指定规则浏览和抽取信息,在抽取信息的基础上,才能进行其他文本的操作。
目前,Linux系统 /bin 目录下有 awk 和 gawk 两个命令,gawk是一种功能很强很实用的语言,利用gawk可以实现数据查找、抽取文件中数据、创建管道流命令等功能。
2、awk编程模型
awk程序由一个主输出循环(main input loop)维持,主输入循环反复执行,直到终止条件被触发。主输入循环无需程序员去写,awk已经搭好主输入循环的框架,程序员写的代码被嵌到主输入循环框架中执行。主输入循环自动依次读取输入文件行,以供处理,而处理文件行的动作由程序员添加。
awk自动完成了打开文件、读取文件行、进行相应处理、关闭文件。
awk定义了两个特殊的字段:BEGIN 和 END,BEGIN用于在主输入循环之前执行,即在未读取输入文件行之前执行,END则相反,用于在主输入循环之后执行,即在读取输入文件行完毕后执行。
awk程序的执行过程如下:
awk编程模型分为三个阶段:读输入文件之前的执行代码段(由BEGIN关键字标识)、读取输入文件时的执行代码段、读输入文件完毕之后的执行代码段(由END关键字标识)。
3、awk调用方法
调用awk的方法与调用sed类似,有三种方式,一种为Shell命令行方式,另外两种是将awk程序写入脚本文件,然后执行该脚本文件。
3.1、Shell命令行输入命令调用awk
awk [-F 分隔符] 'awk程序段' 输入文件
需要使用单引号将awk程序段引起来。
3.2、将awk程序段插入脚本文件,然后通过awk命令调用
awk -f awk脚本文件 输入文件
3.3、将sed命令插入脚本文件后,将脚本文件设置为可执行,然后直接执行该脚本文件
./awk脚本文件 输入文件
awk脚本文件仍以sha-bang(#!)符号开头,但,sha-bang符号后面加上awk或gawk的路径。
4、awk编程示例
4.1、awk模式匹配
任何awk语句都有模式(pattern)和动作(action)组成。模式是一组用于测试输入行是否需要执行动作的规则,动作是包含语句、函数和表达式的执行过程。简言之,模式决定动作何时触发和触发事件,动作执行对输入行的处理。
awk模式匹配经常需要用到正则表达式,awk支持所有正则表达式元字符,awk支持"?"和"+"两个拓展元字符,而grep和sed不支持。
4.1.1、awk的Shell命令行
单引号中间的是awk命令,该awk命令有两部分组成,以/符号分割,^$部分是模式,花括号部分是动作,该awk命令表示一旦读入的输入文件行是空行,就打印 "This is blank line"。
^$是正则表达式,表示空白行,print表示该动作是打印操作,input.txt是输入文件名称。
4.1.2、awk命令写入脚本调用
创建first.awk文件,文件内容如下:
/^$/{print "This is a blank line."}
创建input.txt测试文件,文件内容如下:
awk input.txt
example
???
awk -f 调用含有awk命令的文件,详情如下:
4.1.3、awk脚本调用
创建awk脚本,内容如下:
#!/bin/awk -f
/^$/{print "Blank Line."}
赋予执行权限并执行awk脚本,结果如下:
4.2、记录和域
awk认为输入文件时结构化的,awk将每个输入文件行定义为记录,行中的每个字符串定义为域,域之间用空格、Tab键或其他符号进行分隔,分隔域的符号叫做分隔符。
上图描述了文本中的一条记录,该条记录由四个域组成,域之间用不同的分隔符分隔。
4.2.1、域操作符
awk定义域操作符 $ 来指定执行动作的域,域操作符 $ 后面跟数字或变量来表示域的位置,每条记录的域从1开始编号,如$1,表示第一个域、$2表示第2个域,$0表示所有域。
新建member.txt文件,详情如下:
zhang san man 18
li si woman 20
wang wu man 28
打印域信息:
域操作符 $ 后可跟变量、或者变量运算表达式:
BEGIN字段中定义 num 和 num2 两个变量并赋值,BEGIN字段中语句是在遍历输入文件文本之前执行的,print语句后跟 $(num+num2) 变量运算表达式,num+num=2,该命令打印member.txt的第二个域。
4.2.2、自定义分隔符
1、-F 选项
awk默认的分隔符是空格,Tab键被看做是连续的空格键。可以使用awk的 -F 选项改变分隔符。
将member.txt文本修改如下:
zhang san man 18|ext
li si woman 20|ext
wang wu man 28|ext
指定"|"作为分隔符,打印第一个域和第二个域,执行结果如下:
注意:F 选项 改变分隔符;f 选项 调用awk脚本。
2、FS环境变量
awk改变分隔符的另一种方式是使用awk环境变量FS,通过在BEGIN字段中设置FS的值来管边分隔符。
调整member.txt内容,使用逗号","分隔,详情如下:
zhang,san,man,18|ext
li,si,woman,20|ext
wang,wu,man,28|ext
使用环境变量 FS 调整分隔符:
BEGIN语句中将FS赋值为逗号,表示以逗号为分隔符来处理文件,print语句设置需要打印的域号。
也可以通过正则表达式将分隔符设置为多个字符。
4.3、关系和布尔运算符
awk定义了一组关系运算符,详情如下:
运算符 |
含义 |
< |
小于 |
> |
大于 |
<= |
小于等于 |
>= |
大于等于 |
== |
等于 |
!= |
不等于 |
~ |
匹配正则表达式 |
!~ |
不匹配正则表达式 |
以/etc/passwd作为输入文件,/etc/passwd记录了Linux系统用户的关键信息,系统的每一个合法用户账号对应于该文件中的一行记录,这行记录定义了每个用户账号的属性。
4.3.1、正则表达式匹配
每一行用户记录的各个域用冒号分隔,字段的顺序和含义: 用户名:口令:用户标识号:组标识号:用户名:用户主目录:命令解释程序。
第1条命令:打印/etc/passwd文件中第1个域匹配root关键字的记录,结果为root用户的记录;
第2条命令:打印/etc/passwd文件中全部域匹配root关键字的记录,结果中operator用户的第6域匹配root;
第3条命令:打印/etc/passwd文件中所有域不匹配nologin关键字的记录。
4.3.2、关系运算符
awk进行模式匹配时,可使用条件语句:
打印第3域小于第4域的所有记录。
4.3.4、布尔运算符
awk布尔运算符及其含义:
运算符 |
含义 |
|| |
逻辑或 |
&& |
逻辑与 |
! |
逻辑非 |
示例如下:
第1个命令,查找/etc/passwd文件中满足条件,第3域等于7或者第4域等于7的记录;
第2个、第3个命令,"=="改为了"~",表示模糊匹配,表示查找域值包含 "7" 这个字符、"10"这个字符的记录。
4.4、表达式
4.4.1、表达式的组成
awk表达式用于存储、操作和获取数据,awk表达式可由数值、字符常量、变量、操作符、函数和正则表达式组合而成。
变量是一个值的标识符,定义awk变量,只需定义一个变量名并将值赋给它即可。变量名只能包含字母、数字和下划线,不能以数字开头。awk变量区分大小写,定义awk变量无须声明变量类型,每个变量有两种类型的值:字符串和数值。
awk根据表达式上下文来确定使用哪个值,变量的默认值为0、默认字符串值为空。
4.4.2、表达式的算术运算
表达式可进行变量和数字之间的算术操作,awk算术运算符详情如下:
运算符 |
含义 |
+ |
加 |
- |
减 |
* |
乘 |
/ |
除 |
% |
模 |
^或** |
乘方 |
++x |
在返回x值之前,x变量加1 |
x++ |
在返回x值之后,x变量加1 |
新建有四个空白行的文档blank.txt,打印空白行标号:
1、自增
统计blank.txt文件空白行,一旦匹配,就执行表达式 x=x+1,然后打印返回值。
++x 与 x++ 的区别:
x++:返回x值后,x变量增加1,x变量初始默认为0;
++x:x变量增加1,再返回x值。
2、平均值
新建expression.txt文件,文件详情如下:
zhang san,shanghai,67,78,80
li si,chongqin,89,60,72
wang wu,henan,65,79,85
计算每个人的平均值,shell命令行方式:
awk脚本方式,新建third.awk脚本,脚本内容如下:
#!/bin/awk -f
BEGIN {FS=","}
{total=$3+$4+$5
avg=total/3
print $1,avg}
定义了两个变量total 和 avg,total是3门成绩的和,avg是3门成绩的平均值。
4.5、系统变量
awk定义了很多内建变量用于设置环境信息,称它们为系统变量。系统变量分两种:一种用于改变awk的默认值;一种用于定义系统值,在处理文本时可以读取这些系统值。
常见的awk环境变量及其意义:
变量名 |
意义 |
$n |
当前记录的第n个域,域间由FS分割 |
$0 |
记录的所有域 |
ARGC |
命令行参数的数量 |
ARGIND |
命令行中当前文件的位置(以0开始标号) |
ARGV |
命令行参数的数组 |
CONVFMT |
数字转换格式 |
ENVIRON |
环境变量关联数组 |
ERRNO |
最后一个系统错误的描述 |
FIELDWIDTHS |
字段宽度列表,以空格键分隔 |
FILENAME |
当前文件名 |
FNR |
浏览文件的记录数 |
FS |
字段分隔符,默认是空格键 |
IGNORECASE |
布尔变量,若为真,则进行忽略大小写的匹配 |
NF |
当前记录中的域数量 |
NR |
当前记录数 |
OFMT |
数字的输出格式 |
OFS |
输出域分隔符,默认是空格键 |
ORS |
输出记录分隔符,默认是换行符 |
RLENGTH |
由match函数所匹配的字符串长度 |
RS |
记录分隔符,默认是空格键 |
RSTART |
由match函数所匹配的字符串的第1个位置 |
SUBSEP |
数组下标分隔符,默认值是\034 |
系统变量演示案例如下:
涉及 FS、NF、NR 和 FILENAME 四个系统变量,BEGINN字段利用FS预设域分隔符为",",中间字段为一条print,依次打印NF、NR 和 $0。
NF为记录的域数量,结果显示为5,说明每条记录有5个域;NR显示当前的记录数,该值根据读取输入文件的进度而变化,读取第1条记录时,NR=1,读到文件末尾 时,NR为该文件所包含的记录数;$0 表示打印记录的所有域。
END字段打印 FILENAME,FILENAME保存了当前的输入文件名。
4.6、格式化输出
awk的一个重要功能是产生报表,报表需要按照预定的格式输出。awk定义了printf输出语句,可以规定输出的格式。
printf的基本语法如下:
printf (格式控制符,参数)
awk格式控制符可分为 修饰符 与 格式符 两种。
awk修饰符及含义:
修饰符 |
含义 |
- |
左对齐 |
width |
域的步长 |
.prec |
小数点右边的位数 |
awk格式符及含义:
格式符 |
含义 |
%c |
ASCII字符 |
%d |
整数型 |
%e |
浮点数,科学记数法 |
%f |
浮点数 |
%o |
八进制数 |
%s |
字符串 |
%x |
十六进制数 |
4.6.1、awk格式符案例
awk格式符演示案例如下:
第1个命令,从域号获取值,$1号域与%s对应,为字符串;$3号域与%d对应,为整数值,两个域之间用Tab键隔开(\t表示Tab键),每输出两个域换行(\n表示换行);
第2个命令,表示输出ASCII字符表中标号为66的值,%c完成数值到ASCII字符的转换;
第3个命令,表示以浮点数的格式输出2023,结果精确到小数后6位。
4.6.2、awk修饰符案例
awk修饰符演示案例如下:
以字符串的格式输出expression.txt的第1和2号域,并对第一个%s进行修饰,-15表示该字符串长度控制为15位并且左对齐,若字符串不足15位,则用空格补全。
在输出的域上补充解释语言,在BEGIN字段中添加响应的输出注释。
修饰符.prec表示输出小数点后的位数,%10.3f表示该浮点数长度控制在10位、小数点后保留3位,且右对齐,printf默认对齐方式是右对齐。
.prec也可单独使用,如%.3f。
4.7、内置字符串函数
awk提供强大的内置字符串函数,用于实现文本的字符串替换、查找以及分隔等功能。
awk字符串函数及含义:
函数名 |
含义 |
gsub(r,s) |
在输入文件中用s替换r |
gsub(r,s,t) |
在t中用s替换r |
index(s,t) |
返回s中字符串第一个t的位置 |
length(s) |
返回s的长度 |
match(s,t) |
测试s是否包含匹配t的字符串 |
split(r,s,t) |
在t上将r分成序列s |
sub(r,s,t) |
将t中第1次出现的r替换为s |
substr(r,s) |
返回字符串r中从s开始的后缀部分 |
substr(r,s,t) |
返回字符串r中从s开始长度为t的后缀部分 |
gsub函数执行字符串替换功能,将第一个字符串替换为第二个字符串。gsub函数有两种形式,第一种形式作用于全部域,即$0,第二种形式作用于域t。
第1个命令,将/etc/passwd文件的第1域上的root字符串替换为newroot字符串,BEGIN字段指定域分隔符和输出的域分隔符;
第2个命令,将/etc/passwd文件全部域上的root字符串替换为newroot字符串,结果显示替换了两行。
index函数,返回第二个字符串在第一个字符串出现的首位置;length函数,返回字符串的长度。
match(s,t)测试s是否包含匹配t的字符串,t可以是一个正则表达式,若匹配成功,返回匹配t的首位置;若不成功,则返回0。
第一个命令在newroot字符串中配置O,awk的默认状态区分大小写。因此匹配不成功,返回0。第二个命令将系统变量IGNORECASE设为1,表示awk不分大小写匹配,匹配成功,返回值为O在newroot中的首位置,为5。
sub(r,s,t)将t中第1次出现的r替换为s,r可为正则表达式,注意,sub函数只替换模式出现的第1个位置。
先定义str变量,赋值为 awk of shell,然后用sub函数将 shell 改成大写的 SHELL。
4.8、向awk脚本传递参数
awk脚本内的变量可以在命令行中进行赋值,实现向awk脚本传递参数,变量赋值放在脚本之后、输入文件之前,格式为:
awk 脚本 parameter=value 输入文件
awk所传递的采纳数可以是自定义的变量,也可以是系统变量。
#!/bin/awk -f
NF!=MAX
{print("The line "NR" does not have "MAX" filds")}
执行pass.awj脚本,输出如下:
4.9、数组
数组用于存储一系列值的变量,可通过索引来访问数组的值,索引需要用中括号括起,数组的基本格式为:
array[index]=value
awk数组无需定义数组类型和大小,可以执行赋值后使用。
4.9.1、关联数组
关联数组是指数组索引可以是字符串、也可以是数字。在大部分编程语言中,数组的索引只能是数字,数组表示了存储值的一系列地址,数组索引是由存储地址的顺序来决定的。
关联数组在索引和数组元素值之间建立起关联,对每一个数组元素,awk自动维护了一对值:索引和数组元素值。关联数组的值无以连续的地址进行存储,因此,关联数组即便可以使用数字作为索引,但该数字索引并不表示数组存储地址的信息。
awk的所有数组都是关联数组。
定义data[3.14]="100",在利用CONVFMT系统变量将3.14变成了3,data[3.14]等价于data[3],因此,打印结果data[3.14]为空值。
awk定义for循环访问关联数组,语法如下:
for (variable in array)
action
array是已定义的数组名,variable是任意指定的变量,可看做是for循环中定义的临时变量。
index in array
若 array[index]存在,则返回1,否则返回0。
4.9.2、split函数
split(r,s,t)函数将字符串以t为分隔符,将r字符串拆分为字符串数组,并存放在t中。
将a/b/c分成3个元素存储在数组中。
4.9.3、数组形式的系统变量
awk系统变量中有两个变量以数组形式提供:ARGV和ENVIRON。ARGC是ARGV数组中元素的个数。
新建argv.awk脚本,内容如下:
#!/bin/awk -f BEGIN { for(x=0;x<ARGC;x++) print ARGV[x] print ARGC }
赋予权限并执行,结果如下:
argv.awk只有BEGIN字段,其中语句利用for循环打印ARGC所有的元素,for循环结束后,打印ARGC。
ARGV[0]中存储的是 awk,即执行该脚本的程序名,ARGV[1]~ARGV[3]是输入的三个采纳数,ARGC=4。
一般来说,ARGC=2,ARGV[0]为awk,ARGV[1]为输入文件名,若没有输入文件,则ARGC=1。
对expression.txt文件中的成绩进行检索,输入姓名,响应成绩,新建 findscore.awk 文件,内容如下:
#!/bin/awk -f BEGIN { FS=","; # 判断是否输入了姓名 if (ARGC > 2) { name=ARGV[1]; delete ARGV[1]; } else { while(!name) { print "Please Enter a name"; getline name< "-" } } } $1~name { print $1,$3,$4,$5 }
在findscore.awk脚本的BEGIN字段中,用if判断ARGC是否大于2,若大于2,表示用户已经输入需要查找的姓名,将ARGV[1]赋给name变量,因为ARGV[0]="awk", ARGV[1]中存储了姓名。若ARGC不大于2,则说明此时未输入姓名,则利用循环提示输入姓名,利用getline函数将输入赋给name变量。
主输入循环变量判断第1号域是否与name变量模糊匹配,若是,则输出第1、3、4、5域的值。
5、sed和awk
sed用于流编辑,将一系列的编辑命令作用于缓冲区中输入文件的副本,从而实现对输入文件的各种编辑操作。
awk是处理结构化文件,指划分为记录或域的文件,并且awk提供printf语句能生成格式化报表。