- 参考原文:https://awk.readthedocs.io/en/latest/index.html
- 本文参考原文,在原文的结构和基础上复现整个使用流程,结合自己的实践旨在让自己加深对awk的理解。
一、AWK入门指南
1.1 起步
本文所有示例都在Ubuntu20.4+GNU Awk 5.0.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.2.0)上完成。可使用如下指令安装, 一般安装系统时都会自带awk.
apt-get install gawk
在安装awk时一开始使用“apt-get install awk”安装, 结果安装失败,提示选择以下安装包安装。具体的区别可以参考这篇文章
original-awk 2012-12-20-6
mawk:i386 1.3.4.20200120-2
gawk:i386 1:5.0.1+dfsg-1
mawk 1.3.4.20200120-2
gawk 1:5.0.1+dfsg-1
创建一个测试用的文档awk-sample, 内容如下:
NAME GENDER HEIGHT(cm) MONTH SALARY
zhao male 150 1 1000
qian female 165 0 500
sun female 177 2 5000
li male 180 0 7000
zhou male 155 5 3000
wu male 170 12 1500
zheng female 167 4 3400
wang male 158 3 8000
打印身高大于1.6米以上的人的总薪资:
$ awk '$3>160 {print $1, $4 * $5}' awk-sample
NAME 0
qian 0
sun 10000
li 0
wu 18000
zheng 13600
从这里可以看出以下几点:
- awk单引号中的结构: 模式+执行操作,模式用于匹配过滤行,操作用于输出结果
- awk默认以按空格来分割,从左到右排序且从1开始,使用$来引用
- 文档中出现的数字可以直接参与运算。区别于C,在C中需要先做类型转换
- 模式中类似C语言中的运算,若要使用字符运算,记得加上双引号
- 这里的逗号输出是相当于空格
为了验证最后一点,尝试做以下操作:
$ awk '$1 == "NAME" {print $1}' awk-sample
NAME
在测试中发现:
- 在字符串的比较中,按照字母ascii值从左到右比较, 这点与C中strcmp()相似
- 在字符串和数值的比较中,似乎字符永远比数值大,测试如下:
$ awk '$1>0 {print $1}' awk-sample
NAME
zhao
qian
sun
li
zhou
wu
zheng
wang
$ awk '$1>65535 {print $1}' awk-sample
NAME
zhao
qian
sun
li
zhou
wu
zheng
wang
$ awk '$1>9999999 {print $1}' awk-sample
NAME
zhao
qian
sun
li
zhou
wu
zheng
wang
$ awk '$1<9999999 {print $1}' awk-sample
$ awk '$1<=9999999 {print $1}' awk-sample
$ awk '$1==9999999 {print $1}' awk-sample
AWK的结构:awk + 程序 + 文件
awk 'program' file1 file2 ...
AWK的程序:(program):pattern { action }
和sed类似,awk的处理方式是按行处理,从第一行到最后一行, 依次匹配模式,然后执行动作。模式和动作不能同时省略。
当程序比较复杂的时候, 可以使用选项-f指定程序文件。
程序文件awk-program
# 程序文件awk-program,注意不再需要单引号
$1>"NAME" {print $1, $3}
运行:
$ awk -f awk-program awk-sample
zhao 150
qian 165
sun 177
li 180
zhou 155
wu 170
zheng 167
wang 158
AWK的文件: file1 file2 …
可以同时处理多个文件,文件也可以指stdin。当不输入文件名称时,终端中接着输入的任意数据行,直到输入一个文件结束信号(Ctrl+C,Ctrl+D)
$ awk '{print $1}'
123 456 789
123
000 111 222
000
Ctrl+C
1.2 简单输出
参考原文中的介绍
在 awk 中仅仅只有两种数据类型: 数值和字符构成的字符串。 awk-sample是 一个包含这类信息的典型文件 – 混合了被空格和(或)制表符分割的数字和词语。
Awk 程序一次从输入文件的中读取一行内容并把它分割成一个个字段, 通常默认情况下, 一个字段是一个不包含任何空格或制表符的连续字符序列。 当前输入的行中的地一个字段被称做 $1, 第二个是 $2, 以此类推. 整个行的内容被定 义为 $0.。每一行的字段数量可以不同。
打印行
awk程序中可以没有模式,这时候print打印每一行, $0代表整行, 所以下面两句是同样上的效果
$ awk '{print}' awk-sample
NAME GENDER HEIGHT(cm) MONTH SALARY
zhao male 150 1 1000
qian female 165 0 500
sun female 177 2 5000
li male 180 0 7000
zhou male 155 5 3000
wu male 170 12 1500
zheng female 167 4 3400
wang male 158 3 8000
$ awk '{print $0}' awk-sample
NAME GENDER HEIGHT(cm) MONTH SALARY
zhao male 150 1 1000
qian female 165 0 500
sun female 177 2 5000
li male 180 0 7000
zhou male 155 5 3000
wu male 170 12 1500
zheng female 167 4 3400
wang male 158 3 8000
打印特定段
$后跟指定的数字, 超出的字段不会打印,如$6
$ awk '{print $1, $3, $6}' awk-sample
NAME HEIGHT(cm)
zhao 150
qian 165
sun 177
li 180
zhou 155
wu 170
zheng 167
wang 158
NF, 字段数量
awk用很多内置变量,NF变量会存储当前输入的行的字段数量,所以$NF就表示最后一个字段,效果如下:
$ awk '{ print NF, $1, $NF }' awk-sample
5 NAME SALARY
5 zhao 1000
5 qian 500
5 sun 5000
5 li 7000
5 zhou 3000
5 wu 1500
5 zheng 3400
5 wang 8000
NR, 行号
$ awk '{print NR, $0}' awk-sample
1 NAME GENDER HEIGHT(cm) MONTH SALARY
2 zhao male 150 1 1000
3 qian female 165 0 500
4 sun female 177 2 5000
5 li male 180 0 7000
6 zhou male 155 5 3000
7 wu male 170 12 1500
8 zheng female 167 4 3400
9 wang male 158 3 8000
以下是awk的内建变量表:
变量名称 | 含义 |
---|---|
$0 | 当前行 |
$1 ~ $n | 当前记录的第n个字段,字段间由FS分隔 |
FS | 输入字段分隔符 默认是空格 |
NF | 当前记录中的字段个数,就是有多少列 |
NR | 已经读出的记录数,就是行号,从1开始 |
RS | 输入的记录他隔符默 认为换行符 |
OFS | 输出字段分隔符 默认也是空格 |
ORS | 输出的记录分隔符,默认为换行符 |
ARGC | 命令行参数个数 |
ARGV | 命令行参数数组 |
FILENAME | 当前输入文件的名字 |
IGNORECASE | 如果为真,则进行忽略大小写的匹配 |
ARGIND | 当前被处理文件的ARGV标志符 |
CONVFMT | 数字转换格式 %.6g |
ENVIRON | UNIX环境变量 |
ERRNO | UNIX系统错误消息 |
FIELDWIDTHS | 输入字段宽度的空白分隔字符串 |
FNR | 当前记录数 |
OFMT | 数字的输出格式 %.6g |
RSTART | 被匹配函数匹配的字符串首 |
RLENGTH | 被匹配函数匹配的字符串长度 |
SUBSEP | \034 |
1.3 高级输出
格式化输出
类似C语言中的代码格式,print, printf的用法都很相似
格式: printf(format, value1, value2, …, valuen)
比如:
$ awk '{ printf("total pay for %s is $%.2f\n", $1, $4 * $5) }' awk-sample
total pay for NAME is $0.00
total pay for zhao is $1000.00
total pay for qian is $0.00
total pay for sun is $10000.00
total pay for li is $0.00
total pay for zhou is $15000.00
total pay for wu is $18000.00
total pay for zheng is $13600.00
total pay for wang is $24000.00
1.4 选择
awk的程序结构:模式 {动作}
在模式中可以使用 >, <, ==,&&,||,! 等这些运算符
$ awk '($1=="wu"||$1=="zhou")&&$5<=2000 { print $0}' awk-sample
wu male 170 12 1500
BEGIN与END
特殊模式 BEGIN 用于匹配第一个输入文件的第一行之前的位置, END 则用于匹配处理过的最后一个文件的最后一行之后的位置。这个程序使用 BEGIN 来输出一个
注意这里print语句之间用了分号。
$ awk 'BEGIN { print "before 1 2 3 4 5"; print ""} {print} END{print "end 1 2 3 4 5"}' awk-sample
before 1 2 3 4 5
NAME GENDER HEIGHT(cm) MONTH SALARY
zhao male 150 1 1000
qian female 165 0 500
sun female 177 2 5000
li male 180 0 7000
zhou male 155 5 3000
wu male 170 12 1500
zheng female 167 4 3400
wang male 158 3 8000
end 1 2 3 4 5
1.5 使用AWK进行计算
计数
用作数字的awk变量的默认初始值为0,不需要初始化
统计月薪资在1500以上的人的个数
$ awk '$5 >=1500 {print $0; sum = sum + 1} END {printf("SUM = %.2f\n", sum)}' awk-sample
NAME GENDER HEIGHT(cm) MONTH SALARY
sun female 177 2 5000
li male 180 0 7000
zhou male 155 5 3000
wu male 170 12 1500
zheng female 167 4 3400
wang male 158 3 8000
SUM = 7.00
计算平均薪资
利用NR计算平均薪资, NR代表当前处理的行号, 到最后输出的时候就代表了总的人数, 然后再通过变量统计总的资金, 最后就可以算出平均值。
awk '$5>0 {sum = sum + $5} END {print NR, "employees";print "total pay is", sum;print "average pay is", sum/NR}' awk-sample
9 employees
total pay is 29400
average pay is 3266.67
找出薪资最高的人
$ awk '$5>emp {emp=$5} END {print emp}' awk-sample
SALARY
这里因为前面也说过,字符串总比数值大, 所以这里输出了SALARY, 可以用NR过滤掉
$ awk '$5>emp&&NR>1 {emp=$5} END {print emp}' awk-sample
8000
字符串连接
$ awk 'NR>1 {names = names $1 " "} END {print "names: " names}' awk-sample
names: zhao qian sun li zhou wu zheng wang
注意只要不使用逗号分割
打印最后一个输入行
$ awk '{last=$0} END {print last}' awk-sample
wang male 158 3 8000
内置函数
除了内建变量, awk还提供了内建函数,比如用来计算字符串长度
$ awk '{print $0, length($0);sum = sum + length($0) + 1} END {print "sum: " sum}' awk-sample
NAME GENDER HEIGHT(cm) MONTH SALARY 35
zhao male 150 1 1000 21
qian female 165 0 500 22
sun female 177 2 5000 22
li male 180 0 7000 19
zhou male 155 5 3000 21
wu male 170 12 1500 20
zheng female 167 4 3400 24
wang male 158 3 8000 21
sum: 214
注意$0不包含换行符
awk内建算术函数:
函数名称 | 含义 |
---|---|
atan2( y, x ) | 返回 y/x 的反正切。 |
cos( x ) | 返回 x 的余弦;x 是弧度。 |
sin( x ) | 返回 x 的正弦;x 是弧度。 |
exp( x ) | 返回 x 幂函数。 |
log( x ) | 返回 x 的自然对数。 |
sqrt( x ) | 返回 x 平方根。 |
int( x ) | 返回 x 的截断至整数的值。 |
rand( ) | 返回任意数字 n,其中 0 <= n < 1。 |
srand( [Expr] ) | 将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。 |
1.6 控制语句
awk提供for if-else while控制语句, 都是仿照C语言的风格。它们仅可以在动作中使用。
if-else语句
$ awk '{if($5>1500) print $0}' awk-sample
NAME GENDER HEIGHT(cm) MONTH SALARY
sun female 177 2 5000
li male 180 0 7000
zhou male 155 5 3000
zheng female 167 4 3400
wang male 158 3 8000
while语句和for语句
# interest1 - 计算复利
# 输入: 钱数 利率 年数
# 输出: 复利值
{ i = 1
while (i <= $3) {
printf("\t%.2f\n", $1 * (1 + $2) ^ i)
i = i + 1
}
}
换成for语句
# interest1 - 计算复利
# 输入: 钱数 利率 年数
# 输出: 每年末的复利
{ for (i = 1; i <= $3; i = i + 1)
printf("\t%.2f\n", $1 * (1 + $2) ^ i)
}
$ awk -f interest1
1000 .06 5
1060.00
1123.60
1191.02
1262.48
1338.23
2000 .23 10
2460.00
3025.80
3721.73
4577.73
5630.61
6925.65
8518.55
10477.82
12887.72
15851.89
1.7 数组
相比于C语言,减少了很多初始化的步骤, 有点像python的风格。
$ awk '{data[NR] = $0} END {print data[NR]}' awk-sample
wang male 158 3 8000
1.8 正则匹配
程序结构: /REG/{action}
REG是正则表达是, awk会将符合正则表达式的行关联到action执行
$ awk '/NAME/{print $0}' awk-sample
NAME GENDER HEIGHT(cm) MONTH SALARY
$ awk '/wu/{print $0}' awk-sample
wu male 170 12 1500
$ awk '/^[a-z]/{print $0}' awk-sample
zhao male 150 1 1000
qian female 165 0 500
sun female 177 2 5000
li male 180 0 7000
zhou male 155 5 3000
wu male 170 12 1500
zheng female 167 4 3400
wang male 158 3 8000
关于正则表达式的详细说明, 可以参考:https://blog.csdn.net/hongrisl/article/details/83818199