awk
开始
实用的 awk 程序通常都很短,只有一两行。
假设有一个文件,叫做 emp.data,这个文件包含有名字、每个小时工资(单位元)、工作时长,每一行代表一个雇员的记录,就像这样
Beth 4.00 0
Dan 3.75 0
Kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
现在你想打印每位雇员的名字以及他们的报酬(每小时工资乘以工作时长),而雇员的工作时长必须大于零。这种类型的工作是 awk 的设计目标之一,所以会很简单。只要键入下面一行即可:
awk '$3 > 0 { print $1, $2 * $3 }' emp.data
该行命令告诉操作系统运行 awk 程序,被运行的程序用单引号包围起来,从输入文件 emp.data 获取数据。被但引号包围的部分是一个完整的 awk 程序。它由一个单独的 模式-动作 语句(pattern-action statement)组成。模式 $3 > 0 扫描每一行输入行,如果该行的第三列(或者说 字段(field)大于零),则动作 { print $1, $2 * $3 } 就会为每一个匹配行打印第一个字段,以及第二与第三个字段的乘积。
如果想知道哪些员工在偷懒,键入
awk '$3 == 0 { print $1 }' emp.data
模式 $3 == 0 匹配第三个字段为零的行,动作 { print $1 } 打印该行的第一个字段。
AWK 程序的结构
每一个 awk 程序都是由一个或多个 模式-动作 语句组成的序列:
pattern {action}
pattern {action}
…
awk 的基本操作是在由输入行组成的序列中,陆续地扫描每一行,搜索可以被模式 匹配(match)的行。
程序 $3 == 0 { print $1 }
由一条单独的 模式-动作 语句组成。在一个 模式-动作 语句中,模式或动作可以省略其一,但不能两者同时省略。
如果一个模式没有动作,例如 $3 == 0
会将每一个匹配行(也就是条件判断为真的行)打印出来。
如果只有动作而没有模式,例如 { print $1 }
对于每一个输入行,动作都会被执行。
因为模式与动作都是可选的,所以用花括号将动作包围起来,以便区分两者。
规则介绍
- awk 的数据只有两种类型:数值、由字符组成的字符串。
- awk 从它的输入中每次读取一行,将行分解为一个个的字段(默认将字段看作是非空白符组成的序列)。
- 当前输入行的第一个字段叫做 $1,第二个是 $2,依次类推。
- 一整行记为 $0。
- 每行的字段数可能不一样。
☆模式(匹配)
1. 表达式
> < == || &&
和 C 语言一样
2. 正则表达式
- 例:
awk '$1 ~ /string/ { print }' tr-181-WiFi.xml
, 2. 语法:expression ~ /r/
、expression !~ /r/
,匹配运算符~
的意思是“被… 匹配”,!~
的意思是“不被… 匹配”。当 expression 的字符串值包含一段能够被正则表达式 r 匹配的子字串时, 第一个模式被匹配; 当不存在这样的子字符串时, 第二个模式被匹配。
3. BEGIN
4. END
BEGIN 与 END
特殊的模式 BEGIN 在第一个文件的第一行之前被匹配,END 在最后一行被处理之后匹配。下面这个程序使用 BEGIN 打印一个标题
liyongjun@Box:~/project/shell$ awk 'BEGIN {print "NAME RATE HOURS" } {print}' emp.data
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
观察下面两者的区别
liyongjun@Box:~/project/shell$ awk '{print $0} {print $0}' emp.data
Beth 4.00 0
Beth 4.00 0
Dan 3.75 0
Dan 3.75 0
Kathy 4.00 10
Kathy 4.00 10
Mark 5.00 20
Mark 5.00 20
Mary 5.50 22
Mary 5.50 22
Susie 4.25 18
Susie 4.25 18
liyongjun@Box:~/project/shell$ awk 'BEGIN {print $0} {print $0}' emp.data
Beth 4.00 0
Dan 3.75 0
Kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
因为 BEGIN 是在第一个输入文件的第一行之前匹配,所以 BEGIN 之后的 {print $0} 不可能打印文件中的内容,也就解释了这里为什么打印空行。
5. 复合模式
|| &&
和 C 语言一样
6. 范围模式
pattern 1 , pattern 2 { statements}
一个范围模式匹配多个输入行, 这些输入行从匹配 pattern 1 的行开始, 到匹配 pattern 2 的行结束 (包括这两行), 对这其中的每一行执行 statements。
☆动作
内建变量 NF
当前行字段的数量。
liyongjun@Box:~/project/shell$ cat emp.data
Beth 4.00 0
Dan 3.75 0
Kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
liyongjun@Box:~/project/shell$ awk '{print NF}' emp.data
3
3
3
3
3
3
内建变量 NR
到目前为止,读取到的行的数量。
我们可以使用 NR 和 $0 为 emp.data 的每一行加上行号
liyongjun@Box:~/project/shell$ awk '{print NR, $0}' emp.data
1 Beth 4.00 0
2 Dan 3.75 0
3 Kathy 4.00 10
4 Mark 5.00 20
5 Mary 5.50 22
6 Susie 4.25 18
,
打印多个变量之间可以加逗号 ‘,',也可以不加,不加逗号的效果如下,可以和上面对比一下
liyongjun@Box:~/project/shell$ awk '{print NR $0}' emp.data
1Beth 4.00 0
2Dan 3.75 0
3Kathy 4.00 10
4Mark 5.00 20
5Mary 5.50 22
6Susie 4.25 18
每行输出中加入自定义字符串
liyongjun@Box:~/project/shell$ awk '{print "total pay for", $1, "is", $2 * $3 }' emp.data
total pay for Beth is 0
total pay for Dan is 0
total pay for Kathy is 40
total pay for Mark is 100
total pay for Mary is 121
total pay for Susie is 76.5
在 print 语句中, 被双引号包围的文本会和字段, 以及运算结果一起输出。
print && printf
print 用于简单快速的输出。
printf 用于格式化输出,几乎可以产生任何种类的输出。
liyongjun@Box:~/project/shell$ awk '{ printf("tatol pay for %s is $%.2f\n", $1, $2 * $3) }' emp.data
tatol pay for Beth is $0.00
tatol pay for Dan is $0.00
tatol pay for Kathy is $40.00
tatol pay for Mark is $100.00
tatol pay for Mary is $121.00
tatol pay for Susie is $76.50
变量
这个程序用一个变量 emp 计算工作时长超过 15 个小时的员工人数:
awk '$3 > 15 { emp = emp + 1 } END { print emp}' emp.data
这个程序搜索每小时工资最高的雇员:
awk '$2 > maxrate { maxrate = $2; maxemp = $1 } END { print "highest hourly rate:", maxrate, "for", maxemp }' emp.data
内建函数
length
这个程序计算每一个人的名字的长度:
awk '{ print $1, length($1) }' emp.data
流程控制语句
if
if else
while
for
do while
break
continue
和 C语言一样
数组
下面这个程序按行逆序显示输入数据:
{ line[NR] = $0 } # remember each input line
END { i = NR # print lines in reverse order
while (i > 0) {
print line[i]
i = i - 1
}
}
这是用 for 循环实现的等价的程序:
{ line[NR] = $0 } # remember each input line
END { for (i = NR; i > 0; i = i - 1)
print line[i]
}
输入
1. 命令行参数
-f
跟文件,文件内容是 awk 的命令
-F
指定分割符
模式与动作的多种组合情况
无模式,单动作
无模式,多动作
单模式,无动作
单模式,单动作
单模式,多动作
多模式,无动作
多模式,单动作
多模式,多动作
多模式之间使用分号 “;” 隔开,多动作之间亦然。
注意,多模式相当于对同一行进行多次匹配,多动作亦然。
实用例程
1. 输入行的总行数
awk 'END { print NR }' emp.data
2. 打印第三行
自己写的
awk '{ line = line + 1 } line == 3 { line_str = $0 } END { print line_str }' emp.data
别人写的…
awk 'NR == 3' emp.data
3. 打印匹配行的下一行
awk '$1 == "Dan" { next_line = NR + 1 } NR == next_line' emp.data
4. 打印每一个输入行的最后一个最后一个字段
awk '{ print $NF }' emp.data
5. 打印最后一行的最后一个字段
awk '{ tmp = $NF} END { print tmp }' emp.data
6. 打印字段数多于 2 个的输入行
awk 'NF > 2' emp.data
7. 打印最后一个字段值大于 4 的输入行
awk '$NF > 4' emp.data
8. 打印所有输入行的字段数的总和
awk '{ sum = sum + NF } END { print sum }' emp.data
9. 打印包含 Beth 的行的数量
awk '/Beth/ { sum = sum + 1 } END { print sum }' emp.data
10. 打印具有最大值的第一个字段, 以及包含它的行 (假设 $1 总是 正的)
awk '$1 > max { max = $1; line = $0 } END { print max, line }' emp.data
11. 打印至少包含一个字段的行
awk 'NF > 0' emp.data
12. 打印长度超过 80 个字符的行
awk 'length($0) > 10' emp.data
13. 在每一行的前面加上它的字段数
awk '{ print NF, $0 }' emp.data
14. 打印每一行的第 1 与第 2 个字段, 但顺序相反
awk '{ print $2, $1 }' emp.data
15. 交换每一行的第 1 与第 2 个字段, 并打印该行
awk '{ tmp = $1; $1 = $2; $2 = tmp ; print }' emp.data
16. 将每一行的第一个字段用行号代替
awk '{ $1 = NR ; print }' emp.data
17. 打印删除了第 2 个字段后的行
awk '{ $2 = "" ; print }' emp.data
18. 将每一行的字段按逆序打印
awk '{ for (i = NF; i >= 1; i--) printf("%s ", $i); print "" }' emp.data
19. 打印每一行的所有字段值之和
awk '{sum = 0; for (i = 1; i <= NF; i++) sum = sum + $i; print sum }' emp.data
20. 将所有行的所有字段值累加起来
awk '{for (i = 1; i <= NF; i++) sum = sum + $i; print sum }' emp.data
21. 将每一行的每一个字段用它的绝对值替换
awk '{for (i = 1; i <= NF; i++) if($i < 0) $1 = -$i; print }' emp.data