开始

实用的 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. 正则表达式

  1. 例: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 用于格式化输出,几乎可以产生任何种类的输出。

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