linux三剑客之awk命令
awk与sed相似,都是用作文本处理,awk的基本功能也是搜索文件中包含某些模式的行,当某行匹配一个模式时,awk就在那一行上执行指定的操作。
awk语法由一个模式(pattern)后面跟一个动作(action)组成,其中动作被包括在花括号内,用来与模式分离,最后加上输入的文件名
awk模式匹配经常用到正则表达式,支持所有的正则表达式元字符以及”?”和”+” 两个扩展元字符,grep和sed则不支持”?” 和”+” 扩展元字符。
例如:匹配a.txt文件中的空白行后打印hello world,执行命令如下:

上述命令中为awk的第一种调用方式,单引号中间的是awk命令,由两部分组成,通过符号/分割,其中^$表示模式,是正则表达式,花括号中的是动作,print表示打印操作
第二种调用方式是通过脚本的形式,脚本名称test.awk ,脚本内容如下:


上图种直接将模式以及动作写入到脚本文件中,然后通过awk -f 进行调用即可
还可以通过类似shell脚本的写法,以sha-bang符号开头,后面接awk解释器位置,解释器可通过which awk查看,如图:

给脚本赋予执行权限chmod +x test.awk , 然后执行脚本,如图:

awk记录和域
awk对于每个输入文件行定义为记录,对于文件行中的每个字符串定义为域,域之间通过空格、tab键或其他符号进行分割

上图中第一个域为hello、第二个域为world、第三个域为ni,以此类推,通过$符号来指定执行动作的域,如$1表示第一个域、$2表示第二个域,以此类推,$0表示打印所有域
1.例如:通过awk命令,打印域值,按照6、5、4、3、2、1的顺序,如图:

上述命令中print后面打印多个值的时候,每个值中间用逗号分隔即可
2. 如果打印所有域,可以使用$0,如图:

3. 域操作符$ 后面还可以跟变量,或者变量运算表达式,如图:

上述命令中先用BEGIN定义变量,print后面跟变量表达式,因此实际awk打印的是第3个域值
注意:BEGIN语句在遍历输入文本之前执行的,因此先定义变量,后执行表达式
4. 上图中的d.txt文件中的域默认都是空格进行分割,现在将内容改为如下:

从图中可以看到,hello与world中间通过冒号(:)进行分割,现在我们通过-F选项指定分隔符来打印域值,如图:

上图中通过-F指定冒号(:)为分隔符,打印第一个域和第二个域的值,可以看到hello变为了第一个域,其余几个变成了第二个域
5. 除了-F选项,还可以使用awk中的环境变量FS来改变分隔符,通过在BEGIN中设置FS的值来定义分隔符,如图:

上图中在BEGIN中指定FS为冒号(:)作为分隔符,并且打印第2个域,注意外层单引号内层为双引号,因此FS位置为双引号,否则报错
关系和布尔运算符
awk定义了一组关系运算符用于模式匹配,运算符及其含义如下
| 运算符 | 含义 |
| < | 小于 |
| > | 大于 |
| <= | 小于等于 |
| >= | 大于等于 |
| == | 等于 |
| != | 不等于 |
| ~ | 匹配正则表达式 |
| !~ | 不匹配正则表达式 |
本例子中以/etc/passwd文件作为操作对象进行演示
1.打印/etc/passwd中第一个域匹配root的记录,执行命令如下:

上图中$1表示第一个域的值,后面表示匹配root记录
2. 打印全部域匹配root的记录,执行命令如下:

上图中$0表示全部域中匹配root的记录
3. 打印所有域中不匹配nologin关键字的记录,执行命令如下:

4. 打印第七个域匹配bash的记录行的第一个域,执行命令如下:

注意:在使用这些运算符的时候,左侧指定一个变量,右侧指定正则表达式或者字符串
5. awk在进行模式匹配的时候,经常使用条件语句,有if、if/else、if/else else三种。
例如:匹配第三个域的值小于第四个域的值的所有记录,执行命令如下:

awk布尔运算符及其含义
| 运算符 | 含义 |
| && | 逻辑与 |
| || | 逻辑或 |
| ! | 逻辑非 |
6. 打印匹配第三个域或者第四个域等于1的所有域,执行命令如下

7. 打印第三个域不等于8的所有域,执行命令如下:

8.打印第三个域不等于8并且第四个域不等于99的所有行,如图:

awk表达式
awk表达式用于存储、操作、和获取数据,表达式可由数值、变量、操作符、字符常量、函数、以及正则表达式自由组成。
变量是一个值的标识符,定义变量awk变量很方便,只需要定义一个变量名并将值赋值给它即可,变量名只能包含字母、数字、下划线,但是不能以数字开头,同时变量名是区分大小写的,定义awk变量无需声明变量类型,每个变量有两种类型的值:字符串和数值,变量的默认数值为0,默认字符串为空
awk算术表达式及其含义
| 运算符 | 含义 |
| + | 加 |
| – | 减 |
| * | 乘 |
| / | 除 |
| % | 模 |
| ^或** | 乘方 |
| ++x | 在返回x值之前,x变量加1 |
| x++ | 在返回x值之后,x变量加1 |
1.统计文件a.txt中空白行,执行命令如下:

上述命令表示当匹配到空行的时候,执行表达式x+=1,也就是x=x+1,并打印返回值,图中表示文件有三个空白行(默认x的值从0开始)
2.通过x++或者++x命令来统计文件中的空白行,执行命令如下:

上述命令中x++表示返回x的值后,x变量加1,x的初始默认值为0

上述命令中++x表示x变量先加1后,再返回x的值,因此打印出来的值都会加1
通过脚本计算平均值:现有一个文件aa.txt,内容如下:

脚本名称为avg.awk,脚本内容如下:

从脚本内容中可以看出,通过逗号作为分隔符,首先定义变量total,计算所有域的和,再次定义变量avg,取total数的平均值,最后通过print进行打印。
系统变量:
awk系统变量可以分为两种,第一种用于改变awk的默认值,如域分割符,第二种用于定义系统值,在处理文本的时候可以读取这些系统值,如域数量、当前记录数、当前文件名等,awk动态改变第二种系统变量的值
awk环境变量及其含义
| 变量名 | 含义 |
| $n | 当前记录的第n个域,域用FS分割 |
| $0 | 记录的所有域 |
| ARGC | 命令行参数的数量 |
| ARGIND | 命令行中当前文件位置(以0开始标号) |
| ARGV | 命令行参数的数组 |
| CONVFMT | 数字转换格式 |
| ENVIRON | 环境变量关联数组 |
| ERRNO | 最后一个系统错误的描述 |
| FIELDWIDTHS | 字段宽度列表,以空格键分割 |
| FILENAME | 当前文件名 |
| FNR | 浏览文件的记录数 |
| FS | 字段分隔符,默认是空格 |
| IGNORECASE | 布尔变量,如果为真,忽略大小写匹配 |
| NF | 当前记录的域数量,当前行字段数 |
| NR | 当前记录数,实际执行时对应于当前行号 |
| OFMT | 数字的输出格式 |
| OFS | 输出域分隔符,默认空格 |
| ORS | 输入记录分隔符,默认换行符 |
| RS | 记录分隔符,默认空格键 |
| RLENGTH | 由match函数所匹配的字符串长度 |
| RSTART | 由match函数所匹配的字符串第一个位置 |
| SUBSEP | 数组下标分隔符,默认是\034 |
例如:文件a.txt的内容如下:

1、打印当前记录有多少行,也就是文件的行数,如图:

2、打印每个记录行中的域的数量,默认是以空格作为分隔符,如图:

a.txt中第一行有三个域,第二行有2个域,第三行有一个域
3、手动设置为逗号为分隔符,然后打印域的个数,如图:

4、打印最后一个域,如下:

注:使用$NF即可打印最后一个域
5、去掉最后一个域和冒号,如下:
echo "field1:field2:field3:field4" | awk -F':' '{for(i=1;i<NF;i++){printf "%s",$i;if(i< NF-1) printf ":"};print ""}'

格式化输出:
awk一大主要功能是产生报表,awk定义了printf输出语句,此语句可以规定输出的格式,基本语法如下:printf (格式控制符,参数),格式控制符以%符号开始
awk格式控制符分为修饰符以及格式符两种
printf修饰符及其含义
| 修饰符 | 含义 |
| – | 左对齐 |
| width | 域的步长 |
| .prec | 小数点右边的位数 |
printf格式符及其含义
| 格式符 | 含义 |
| %c | ASCII字符 |
| %d | 整型数 |
| %e | 浮点数,科学计数法 |
| %f | 浮点数 |
| %o | 八进制数 |
| %s | 字符串 |
| %x | 十六进制数 |
例子:现有一文件bb.txt,文件内容如下:

执行命令,打印输出记录行中的第一个域(字符串)以及第三个域(整数),如图:

上图中以逗号为分隔符,%s表示打印字符串,与$1对应,%d表示打印整数,与$4对应,\t表示打印完第一个字符串之后加一个tab键,\n表示打印完第一个记录行数据后,进行换行。注意使用的是printf命令,同时格式控制符通过双引号引起来
执行命令对第一个字符串进行修饰,将字符串长度控制在10位,不足用0补齐,如图:

内置字符串函数
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中第一次出现的r替换成s |
| substr(r,s) | 返回字符串r中从s开始的后缀部分 |
| substr(r,s,t) | 返回字符串r中从s开始长度为t的后缀部分 |
将/etc/passwd文件中第一个域中的root字符串替换成test,执行命令如图:

上图中OFS表示打印域分隔符,注意FS与OFS之间用分号间隔,gsub函数表示匹配root字符串替换成test,$1表示第一个域,$0表示打印所有匹配的域
如果要替换全部的root字符串为test,只需要将$1去掉即可,如图:

index()函数表示返回第二个字符串在第一个字符串出现的位置,注意要使用BEGIN命令,如图:

length()函数用于计算字符串的长度,如图:



