awk的主要作用是在可以针对查找文本定义模块化功能,实现复杂的字符串操作.awk是一个行文本处理命令,会通过默认的记录分割符(RS)将文本分割为多个记录,然后使用域分割符(FS)来将记录内容进行分割并赋值给临时的变量进行相关的操作.
基本介绍
通过定义不同的条件完成指定的操作,基本格式如下:
awk '条件1 {动作 1} 条件2{动作 2} …' 文件名
- 条件(Pattern):
awk支持的主要条件类型
条件类型 | 条 件 说 明 |
---|---|
awk保留字 BEGIN | 在 awk 程序一开始,尚未读取任何数据之前执行。BEGIN 后的动作只在程序开始时执行一次 |
awk保留字 END | 在 awk 程序处理完所有数据,即将结束时执行?END 后的动作只在程序结束时执行一次 |
关系运算符: > 大于 < 小于 >= 大于等于 <= 小于等于 == 等于 != 不等于 | 用于判断两个值之间的逻辑关系。如果是给变量赋值,则使用"=” |
A~B包含 | 判断字符串 A 中是否包含能匹配 B 表达式的子字符串,A!~B 判断字符串 A 中是否不包含能匹配 B 表达式的子字符串 |
正则表达式 /正则/ | 如果在“//”中可以写入字符,则也可以支持正则表达式 |
动作(Action)
- 格式化输出
- 流程控制
在默认处理中,awk会首先使用默认的记录分割符(Record Seperator,缩写为RS,默认的记录分割符为"\n")进行记录分割,而默认的记录分割符是"\n",所以首先会将文本按照"\n"进行划分为不同的记录;然后会使用默认的域分割符(Field Seperator,缩写为FS,默认的域分割符为" ")将单条记录分割为不同的域,使用$1,$2...进行临时存储,便于后续的操作.其中$0指代当前整条记录,$1指代当前的记录的第一个字段,$2指代当前记录中的第二个字段,依次类推.
简单实用
为了方便说明,创建一个文本文件,命名为records.txt
Name PHP C Java Note
Jack 98 89 90 swimming&basketball&hiking
Frank 87 66 78 climming;swimming;football
Duke 78 99 83 basketball
Ericy 88 89 90 basketball
- 首先尝试使用awk输出Records中的所有内容:
awk '{print}' Records.txt
#或者使用
awk '{print $0}' Records.txt
在该条指令中,省略了判断条件每一条记录都会执行该输出,而使用print省略参数的情况下默输出整条记录,所以执行该条指令会输出所有的记录.而有的时候,也可以根据需要省略执行语句:
cat Records.txt| awk '/ac/'
省略时默认输入满足条件的行,以上的命令这样就可以筛选出所有包含ac的行.由此可见,条件和动作并不是必须的,在适当的情况下是可以省略的(但是条件和动作不能同时省略).
- 输出姓名和C语言的成绩
可以使用如下命令:
awk '{print $1,$3}' Record.txt
在该条指令中,由于读取的记录会按照默认的FS进行分割,所以一条记录中,姓名会被赋值给$1,PHP成绩赋值给$2,C语言成绩赋值给$3, Java成绩赋值给$4,所以只需要针对每条记录输出 $1,$3即可.得到如下输出:
Name C
Jack 89
Frank 66
Duke 99
Ericy 89
由于第一行的Name和C并不是一个记录而是一个描述字段,可不可以把他们去掉呢?当然可以,在awk中有两个自带的变量NR(Numner of row)和NF(Number of field),相当于行号和列号,所以只需要隔离掉第一行的输出就可达到目的.
awk 'NR!=1{print $1,$3}' Record.txt
当然,可以把第一行当作一个特殊的记录,输出时隔离掉这条特殊的记录就可以了,同样可以达到目的.
awk '$1!="Name"{print $1,$3}' Record.txt
由于在输出时使用","进行参数分割,awk在输出时会使用默认的域分割符进行各个字段的分割.这时候可能会觉得,如果能让每一列能够下对齐看起来会舒服很多.想要实现这个功能只需要设置一个自定义域输出分割符就可以了,例如可以这样:
awk '$1!="Name"{print $1 "\t" $3}' Records.txt
除了这样还可以使用另一种方式,那就是自定义域输出分割符(OFS).
- 自定义域输出分割符
在awk中包含了两个内置的条件保留关键字,BEGIN和END.关键字BEGIN可以保证在尚未读取任何数据之前执行,而关键字END可以保证在程序处理完所有数据之后执行,而且这两条指令都只执行一次.所以针对以上的域对齐需求可以使用自定义域输出分割符来实现:
awk 'BEGIN{OFS="\t"} $1!="Name"{print $1, $3}' Records.txt
在你需要的时候,还可以自定义FS,RS,OFS,ORS等来实现需求.
- 输出PHP成绩大于85的人员姓名
只需要在原来的输出条件上添加控制成绩的判断即可:
awk 'BEGIN{OFS="\t"} $1!="Name" && $2>85 {print $1}'
- 输出姓名以字母k结尾的人员的姓名和C语言成绩:
可以使用正则表达式来做筛选:
awk 'BEGIN{OFS="\t"} $1~/k$/{print $1, $3}' Records.txt
如果需要筛选不符合条件的人员姓名,只需要在条件前添加取反即可.例如查找姓名不是以字符k结尾的人员的姓名和C语言成绩:
awk 'BEGIN{OFS="\t"} $1!="Name" && !($1~/k$/){print $1, $3}' Records.txt
- 求出Java的平均成绩
在awk中可以自定义的变量,统计平均成绩首先就需要统计所有成绩的和,然后在END条只执行时求去平均成绩.
awk 'BEGIN{OFS="\t"} $1!="Name"{java=java+$4} END{print java/(NR-1)}' Records.txt
当然还也可以不使用NR,使用自定义计数变量来实现:
awk 'BEGIN{OFS="\t"} $1!="Name"{java=java+$4;counter=counter+1;} END{print java/counter}' Records.txt
- 求出Java的成绩分布
cat Record.txt | awk 'NR!=1{a[$4]++} END{for(I in a) print I "\t" a[I]}'
- 求出Java成绩的最高分
cat Record.txt | awk '$1!="Name" && $4 > max {max=$4} END{print "max mark is", max}'
- 自定义输出数据格式
一般情况下简单的信息输出使用print即可,该输出可以直接输出所需要的信息,并自动换行.但是如果对数据输出格式有特别的要求的话,可以使用printf来进行定义.
printf 语句的形式如下:
printf(format, value1, value2, ..., valuen)
format是格式控制字符串,其中包含了需要逐字输出的字符串和需要输出的数据的指定格式.数据格式一般使用%+对应的数据格式表示.printf不能自动换行,所以对于行数据输出,需要在format中手动添加"\n".
姓名按照至少10个字符左对齐,平均成绩使用6位数字且小数部分2位输出
cat Record.txt | awk '$1!="Name"{printf("%-8s %6.2f\n", $1, ($2+$3+$3)/(NF-1))}'
也可以在format中添加需要逐字输出的定义信息:
cat Record.txt | awk '$1!="Name"{printf("%-8s :average is %6.2f\n", $1, ($2+$2+$3)/ (NF-1))}'
- 排序
使用sort对输出结果进行排序,默认会对第一个域(Field)进行排序.
按照姓名的升序进行排序查看C语言成绩
cat Record.txt | awk '$1!="Name"{printf("%-8s %6.2f\n", $1, $3)}' | sort
按照姓名的降序进行排序查看Java语言成绩
cat Record.txt | awk '$1!="Name"{printf("%-8s %6.2f\n", $1, $4)}' | sort -r
按照Java成绩升序输出姓名和Java成绩(-b忽略前导的空白区域 -k 指定排序的列)
cat Record.txt | awk '$1!="Name"{printf("%-8s %6.2f\n", $1, $4)}' | sort -b -k 2
- 控制语句
- if-else语句
- while语句
- for语句
if-else是常用的控制语句,其实在上边使用到的条件语句都是一种省略形式的if-else语句.
awk 'NR!=1{print $1,$3}' Record.txt
//等价于
awk '{if(NR!=1)print $1, $3}' Record.txt
awk 'BEGIN{OFS="\t"} $1!="Name"{print $1, $3}' Records.txt
//等价于
awk 'BEGIN{OFS="\t"} {if($1!="Name") print $1, $3}' Records.txt
while主要用于循环实现,例如将Record.txt中的信息逆序输出:
#定义一个实现文本script
{list[NR]=$0}
END{
j=NR
while(j>0) {
print list[j];
j--;
}
}
#使用awk -f执行脚本
cat Record.txt | awk -f script
同样也可以使用for实现:
#定义一个实现文本script
{list[NR]=$0}
END{
for(j=NR;j>0;j--) {
print list[j];
}
}
#使用awk -f执行脚本
cat Record.txt | awk -f script