Awk 是一种使用方便且表现力很强的编程语言, 它可以应用在多种不同的计算与数据处理任务中.
选择
Awk 的模式非常擅长从输入中选择感兴趣的行, 以便进行进一步的处理. 因为一个没有动作的模式会将所有匹配的行打印出来, 所以许多 awk 程序仅含有一条单独的模式. 这一节给出的的例子, 其模式具有很高的实用价值.
通过比较进行选择
这个程序使用一个比较模式来选择某些雇员的记录, 条件是他的每小时工资大于等于 $5.00, 也就是
第二个字段大于等于 5:
$2 >= 5
它从 emp.data 中选择这些行:
Mark 5.00 20
Mary 5.50 22
通过计算进行选择
这个程序使用一个比较模式来选择某些雇员的记录, 条件是他的每小时工资大于等于 $5.00, 也就是第二个字段大于等于 5:
$2 >= 5
它从 emp.data 中选择这些行:
Mark 5.00 20
Mary 5.50 22
通过文本内容选择
除了数值选择, 用户也可以选择那些包含特定单词或短语的输入行. 这个程序打印所有第一个字段
是 Susie 的行:
$1 == “Susie”
操作符 == 测试相等性. 用户也可以搜索含有任意字母, 单词或短语的文本, 通过一个叫做 正则表达式
(regular expressions) 的模式来完成. 这个程序打印所有包含 Susie 的行:
/Susie/
输出是
Susie 4.25 18
正则表达式可以用来指定非常精细的模式.
模式的组合
模式可以使用括号和逻辑运算符进行组合, 逻辑运算符包括 &&, ||, 和 !, 分别表示 AND, OR, 和
NOT. 程序
$2 >= 4 || $3 >= 20
打印那些 $2 至少为 4, 或者 $3 至少为 20 的行:
Beth 4.00 0
Kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
两个条件都满足的行只输出一次. 将这个程序与下面这个程序作对比, 它包含两个模式:
$2 >= 4
$3 >= 20
如果某行对这两个条件都满足, 它会被打印两次
Beth 4.00 0
Kathy 4.00 10
Mark 5.00 20
Mark 5.00 20
Mary 5.50 22
Mary 5.50 22
Susie 4.25 18
注意程序
!(\$2 < 4 && $3 < 20)
打印的行不满足这样的条件: $2 小于 4, 并且 $3 也小于 20; 这个条件判断与上面的第一个等价, 虽然在
可读性方面差了一点.
数据验证
真实的数据总是存在错误. 检查数据是否具有合理的值, 格式是否正确, 这种任务通常称作 数据验证
(data validation), 在这一方面 awk 是一款非常优秀的工具.
数据验证在本质上是否定: 不打印具有期望的属性的行, 而是打印可疑行. 接下来的程序使用比较
模式, 将 5 条合理性测试应用到 emp.data 的每一行: 11
NF != 3 { print $0, “number of fields is not equal to 3” }
$2 < 3.35 { print $0, “rate is below minimum wage” }
$2 > 10 { print $0, “rate exceeds $10 per hour” }
$3 < 0 { print $0, “negative hours worked” }
$3 > 60 { print $0, “too many hours worked” }
如果数据没有错误, 就不会有输出.
BEGIN 与 END
特殊的模式 BEGIN 在第一个输入文件的第一行之前被匹配, END 在最后一个输入文件的最后一行
被处理之后匹配. 这个程序使用 BEGIN 打印一个标题:
BEGIN { print “NAME RATE HOURS”; print “” }
{ print }
输出是
NAME RATE HOURS
Beth 4.00 0
Dan 3.75 0
Kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
可以在同一行放置多个语句, 语句之间用分号分开. 注意 print “” 打印一个空行, 它与一个单独的
print 并不相同, 后者打印当前行.
用AWK计算
一个动作就是一个语句序列, 语句之间用分号或换行符分开. 读者已经见过只有一条单独的 print语句的动作. 这一小节提供的例子所包含的语句可以用来进行简单的数学或字符串计算. 在这些语句里,不仅可以使用内建变量, 比如 NF, 还可以自己定义变量, 这些变量可以用来计算, 存储数据等. 在 awk中, 用户创建的变量不需要事先声明就可以使用.
计数
这个程序用一个变量 emp 计算工作时长超过 15 个小时的员工人数: 12
$3 > 15 { emp = emp + 1 }
END { print emp, “employees worked more than 15 hours” }
对每一个第三个字段超过 15 的行, 变量 emp 的值就加 1. 用 emp.data 作输入数据, 这个程序输出:
3 employees worked more than 15 hours
当 awk 的变量作为数值使用时, 默认初始值为 0, 所以我们没必要初始化 emp.
计算总和与平均数
为了计算雇员的人数, 我们可以使用内建变量 NR, 它的值是到目前为止读取到的行数; 当所有输入都处理完毕时, 它的值就是读取到的行数.
END {print NR, “employees” }
输出是:
6 employees
这里有个程序利用 NR 来计算平均报酬:
{ pay = pay + $2 * $3 }
END { print NR, “employees”
print “total pay is”, pay
print “average pay is”, pay / NR
}
第一个动作累加所有雇员的报酬. END 动作打印:
6 employees
total pay is 337.5
average pay is 56.25
很明显, printf 可以用来产生更加美观的输出. 这个程序有一个潜在的错误: 一种不常见的情况是 NR
的值为 0, 程序会尝试将 0 作除数, 此时 awk 就会产生一条错误消息.
操作文本
Awk 的长处之一是它可以非常方便地对字符串进行操作, 就像其他大多数语言处理数值那样方便.
Awk 的变量除了可以存储数值, 还可以存储字符串. 这个程序搜索每小时工资最高的雇员:
$2 > maxrate { maxrate = $2; maxemp = $1 }
END { print “highest hourly rate:”, maxrate, “for”, maxemp }
它的输出是:
highest hourly rate: 5.50 for Mary
在这个程序里, 变量 maxrate 保存的是数值, 而 maxemp 保存的是字符串. (如果有多个雇员都拥有相
同的最高每小时工资, 这个程序只会打印第一个人的名字.)
字符串拼接
可以通过旧字符串的组合来生成一个新字符串; 这个操作叫作 拼接 (concatenation). 程序
{ names = names $1 “ “ }
END { print names }
将所有雇员的名字都收集到一个单独的字符串中, 每一次拼接都是把名字与一个空格符添加到变量
names 的值的末尾. names 在 END 动作中被打印出来:
Beth Dan Kathy Mark Mary Susie
在一个 awk 程序中, 字符串的拼接操作通过陆续写出字符串来完成. 对每一个输入行, 上面程序中的第一条语句将三个字符串连接在一起: names 早先的值, 第一个字段, 以及一个空格; 然后再将结果字符串
赋值给 names. 于是, 当所有的输入行都读取完毕时, names 包含有一个由所有雇员名字组成的, 每个名字之间由空格分隔的字符串. 用来存储字符串的变量的初始值默认为空字符串 (也就是说该字符串不包含任何字符), 因此在这个程序里, names 不需要显式地初始化.
打印最后一行
虽然在 END 动作里, NR 的值被保留了下来, 但是 $0 却不会. 程序
{ last = $0 }
END { print last }
可以用来打印文件的最后一行:
Susie 4.25 18
内建函数
我们已经看到 awk 提供有内建变量, 这些变量可以用来维护经常需要用到的量, 比如字段的个数,
以及当前输入行的行号. 同样, awk 也提供用来计算其他值的内建函数. 求平方根, 取对数, 随机数, 除了
这些数学函数, 还有其他用来操作文本的函数. 其中之一是 length, 它用来计算字符串中字符的个数.
例如, 这个程序计算每一个人的名字的长度:
{ print \$1, length($1) }
程序运行结果是: 14
Beth 4
Dan 3
Kathy 5
Mark 4
Mary 4
Susie 5
行,单词与字符的计数
这个程序使用 length, NF 与 NR 计算行, 单词与字符的数量, 为方便起见, 我们将每个字段都当成
一个单词.
{ nc = nc + length($0) + 1
nw = nw + NF
}
END { print NR, “lines,”, nw, “words,”, nc, “characters” }
文件 emp.data 含有
6 lines, 18 words, 77 characters
我们为每一个输入行末尾的换行符加 1, 这是因为 $0 不包含换行符.
注:The AWK Programming Language学习笔记