Shell(四):awk编程

发布时间 2023-10-12 20:36:06作者: 无虑的小猪

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程序的执行过程如下:

  0

  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命令行

  0

  单引号中间的是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脚本,结果如下:

  0

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

  打印域信息:

  0

  域操作符 $ 后可跟变量、或者变量运算表达式:

  0

  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

  指定"|"作为分隔符,打印第一个域和第二个域,执行结果如下:

  0

  注意: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 调整分隔符:

  0

  BEGIN语句中将FS赋值为逗号,表示以逗号为分隔符来处理文件,print语句设置需要打印的域号。

  也可以通过正则表达式将分隔符设置为多个字符。

4.3、关系和布尔运算符

  awk定义了一组关系运算符,详情如下:

运算符

含义

<

小于

>

大于

<=

小于等于

>=

大于等于

==

等于

!=

不等于

~

匹配正则表达式

!~

不匹配正则表达式

  以/etc/passwd作为输入文件,/etc/passwd记录了Linux系统用户的关键信息,系统的每一个合法用户账号对应于该文件中的一行记录,这行记录定义了每个用户账号的属性。

4.3.1、正则表达式匹配

  0

  每一行用户记录的各个域用冒号分隔,字段的顺序和含义: 用户名:口令:用户标识号:组标识号:用户名:用户主目录:命令解释程序。

  0

  第1条命令:打印/etc/passwd文件中第1个域匹配root关键字的记录,结果为root用户的记录;

  第2条命令:打印/etc/passwd文件中全部域匹配root关键字的记录,结果中operator用户的第6域匹配root;

  第3条命令:打印/etc/passwd文件中所有域不匹配nologin关键字的记录。

4.3.2、关系运算符

  awk进行模式匹配时,可使用条件语句:

  0

 

  打印第3域小于第4域的所有记录。

4.3.4、布尔运算符

  awk布尔运算符及其含义:

运算符

含义

||

逻辑或

&&

逻辑与

!

逻辑非

  示例如下:

  0

  第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,打印空白行标号:

  0

1、自增

  统计blank.txt文件空白行,一旦匹配,就执行表达式 x=x+1,然后打印返回值。

  ++x 与 x++ 的区别:

  0

  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命令行方式:

0

  awk脚本方式,新建third.awk脚本,脚本内容如下:

#!/bin/awk -f
BEGIN {FS=","}
{total=$3+$4+$5
avg=total/3
print $1,avg}
  赋予执行权限并执行脚本:

  0

  定义了两个变量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

  系统变量演示案例如下:

0

  涉及 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 (格式控制符,参数)
  printf语句包含两部分,第一部分是格式控制符,以 % 符号开始,用以描述格式规范;第二部分是参数列报表,与格式控制符相对应,是输出的对象。

  awk格式控制符可分为 修饰符 与 格式符 两种。

  awk修饰符及含义:

修饰符

含义

-

左对齐

width

域的步长

.prec

小数点右边的位数

  awk格式符及含义:

格式符

含义

%c

ASCII字符

%d

整数型

%e

浮点数,科学记数法

%f

浮点数

%o

八进制数

%s

字符串

%x

十六进制数

4.6.1、awk格式符案例

  awk格式符演示案例如下:

  0

  第1个命令,从域号获取值,$1号域与%s对应,为字符串;$3号域与%d对应,为整数值,两个域之间用Tab键隔开(\t表示Tab键),每输出两个域换行(\n表示换行);

  第2个命令,表示输出ASCII字符表中标号为66的值,%c完成数值到ASCII字符的转换;

  第3个命令,表示以浮点数的格式输出2023,结果精确到小数后6位。

4.6.2、awk修饰符案例

  awk修饰符演示案例如下:

  0

  以字符串的格式输出expression.txt的第1和2号域,并对第一个%s进行修饰,-15表示该字符串长度控制为15位并且左对齐,若字符串不足15位,则用空格补全。

  在输出的域上补充解释语言,在BEGIN字段中添加响应的输出注释。

  0

  修饰符.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。

  0

  第1个命令,将/etc/passwd文件的第1域上的root字符串替换为newroot字符串,BEGIN字段指定域分隔符和输出的域分隔符;

  第2个命令,将/etc/passwd文件全部域上的root字符串替换为newroot字符串,结果显示替换了两行。

  0

  index函数,返回第二个字符串在第一个字符串出现的首位置;length函数,返回字符串的长度。

  0

  match(s,t)测试s是否包含匹配t的字符串,t可以是一个正则表达式,若匹配成功,返回匹配t的首位置;若不成功,则返回0。

  第一个命令在newroot字符串中配置O,awk的默认状态区分大小写。因此匹配不成功,返回0。第二个命令将系统变量IGNORECASE设为1,表示awk不分大小写匹配,匹配成功,返回值为O在newroot中的首位置,为5。

  0

  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所传递的采纳数可以是自定义的变量,也可以是系统变量。

  新建pass.awk脚本,内容如下:
#!/bin/awk -f
NF!=MAX
{print("The line "NR" does not have "MAX" filds")}

  执行pass.awj脚本,输出如下:

  0
  在输入文件之前加上两条赋值语句,分别对MAX和FS变量赋值,语句之间用空格分隔开,需注意,"="符号两端不能有空格,因为expression有5个域,因此,每条记录下方都输出脚本中的语句。
  0
  输出expression.txt的所有记录,每条记录前加上行号(输出NR变量值),然后重新定义OFS的值,改变输出分隔符,即 NR 和 $0 之间用OFS定义的值分隔。
  命令行参数不能被 BEGIN 字段语句访问,直到输入文件的第1行被读取时,命令行参数方才生效。原因在于,awk读到命令行参数的赋值语句时,并不知道这是一个命令行参数的赋值语句,而认为只是一个文件名,这个文件名是无效的,awk继续读取后面的参数,直到一个正确的输入文件名被解析的时候,awk才判定前面的语句是命令行参数的赋值语句。
  0
  命令BEGIN字段打印n变量值,awk主输入循环中判断n的值,分别输出不同的语句,通过命令行将n赋为1。
  该命令在读到n==1这条赋值语句时,它将n=1当做输入文件名,该命令在读取n=1这个输入文件前执行BEGIN字段,此时n为空,因此打印一行空白行;接着,该命令发现n=1并非有效的文件名,继续读到expression.txt参数,发现是一个有效的文件名,进而将n=1解析为命令行采纳数赋值语句,打印满足n==1时的语句。

4.9、数组

  数组用于存储一系列值的变量,可通过索引来访问数组的值,索引需要用中括号括起,数组的基本格式为:

array[index]=value

  awk数组无需定义数组类型和大小,可以执行赋值后使用。

4.9.1、关联数组

  关联数组是指数组索引可以是字符串、也可以是数字。在大部分编程语言中,数组的索引只能是数字,数组表示了存储值的一系列地址,数组索引是由存储地址的顺序来决定的。

  关联数组在索引和数组元素值之间建立起关联,对每一个数组元素,awk自动维护了一对值:索引和数组元素值。关联数组的值无以连续的地址进行存储,因此,关联数组即便可以使用数字作为索引,但该数字索引并不表示数组存储地址的信息。

  awk的所有数组都是关联数组。

0

  定义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循环中定义的临时变量。

  关键字in也可用在条件表达式中判断元素是否在数组中,条件表达式格式为:
index in array

  若 array[index]存在,则返回1,否则返回0。

  

4.9.2、split函数

  split(r,s,t)函数将字符串以t为分隔符,将r字符串拆分为字符串数组,并存放在t中。

0

  将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
}

  赋予权限并执行,结果如下:

  0

  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。

  0

  对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域的值。

  0

5、sed和awk

  sed用于流编辑,将一系列的编辑命令作用于缓冲区中输入文件的副本,从而实现对输入文件的各种编辑操作。

  awk是处理结构化文件,指划分为记录或域的文件,并且awk提供printf语句能生成格式化报表。