awk(1)-快速入门(上)

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 的设计目标之一, 所以会很简单. 只要键入下面一行即可:

1
2
3
4
5
# awk '$3 > 0 { print $1, $2 * $3 }' emp.data
Kathy 40
Mark 100
Mary 121
Susie 76.5

该行命令告诉操作系统运行 awk 程序, 被运行的程序用单引号包围起来, 从输入文件 emp.data 获取 2
数据. 被单引号包围的部分是一个完整的 awk 程序. 它由一个单独的 模式– 动作 语句 (pattern-action
statement) 组成. 模式 $3 > 0 扫描每一个输入行, 如果该行的第三列 (或者说 字段 (field)) 大于零, 则
动作
{ print \$1, \$2 * $3 }
就会为每一个匹配行打印第一个字段, 以及第二与第三个字段的乘积.
如果想知道哪些员工在偷懒, 键入

1
2
3
# awk '$3 == 0 { print $1 }' emp.data
Beth
Dan

模式 $3 == 0 匹配第三个字段为零的行, 动作

{ print $1 }

打印该行的第一个字段.

AWK程序的结构

现在让我们回退一步, 看一下到底发生了什么. 在上面的命令行中, 被单引号包围的部分是使用 awk
语言编写的程序. 本章的每一个 awk 程序都是由一个或多个 模式–动作 语句组成的序列:
pattern { action }
pattern { action }

awk 的基本操作是在由输入行组成的序列中, 陆续地扫描每一行, 搜索可以被模式 匹配 (match) 的行.
“匹配” 的精确含义依赖于问题中的模式, 比如, 对于 $3 > 0, 意味着 “条件为真”.
每一个输入行轮流被每一个模式测试. 每匹配一个模式, 对应的动作 (可能包含多个步骤) 就会执行.
然后下一行被读取, 匹配重新开始. 这个过程会一起持续到所有的输入被读取完毕为止.
上面的程序是模式与动作的典型例子. 程序

$3 == 0 { print $1 }
由一条单独的 模式–动作 语句组成: 如果某行的第 3 个字段为 0, 那么它的第 1 个字段就会被打印出来.

在一个 模式–动作 语句中, 模式或动作可以省略其一, 但不能两者同时被省略. 如果一个模式没有
动作, 例如
$3 == 0
会将每一个匹配行 (也就是条件判断为真的行) 打印出来. 这个程序将文件 emp.data 中第 3 个字段为
0 的两行打印出来:
Beth 4.00 0
Dan 3.75 0
如果只有动作而没有模式, 例如

{ print $1 }
对于每一个输入行, 动作 (在这个例子里是打印第 1 个字段) 都会被执行.
因为模式与动作都是可选的, 所以用花括号将动作包围起来, 以便区分两者.

运行AWK程序

运行一个 awk 程序有多种方式. 可以键入下面这种形式的命令

1
# awk 'program' input files

这个命令对指定的输入文件的每一行, 执行 program. 例如你可以键入

1
# awk '$3 == 0 { print $1 }' file1 file2

打印文件 file1 与 file2 的每一行的第一个字段 (条件是该行的第 3 个字段为 0).
也可以在命令行上省略输入文件, 只要键入

1
# awk 'program'

在这种情况下, awk 会将 program 应用到你接下来在终端输入的内容上面, 直到键入一个文件结束标志(Unix 系统是组合键 Control-d). 下面是一个在 Unix 上运行的例子

1
2
3
4
5
6
7
8
9
#  awk '$3 == 0 { print $1 }'
Beth 4.00 0
Beth
Dan 3.75 0
Dan
Kathy 3.75 10
Kathy 3.75 0
Kathy
...

Beth、Dan、Kathy为awk打印的字符

这种行为对测试 awk 程序来说非常方便: 键入程序与数据, 检查程序的输出. 我们再次建议读者运
行并修改书中的程序.
注意到, 命令行中的程序被单引号包围. 这个规定可以防止程序中的字符 (例如 $) 被 shell 解释, 也
可以让程序的长度多于一行.

当程序的长度比较短时 (只有几行), 这种安排会比较方便. 如果程序比较长, 更好的做法是将它们放
在一个单独的文件中, 如果文件名是 progfile 的话, 运行时只要键入

1
# awk -f progfile optional list of files

选项 -f 告诉 awk 从文件中提取程序. 在 progfile 出现的地方可以是任意的文件名.

简单输出

我们只将程序的主体显示出来, 而不是完整的命令行. 在每一种情况下, 程序或者可以被包围在一对
单引号中, 作为 awk 命令的第一个参数来运行, 也可以将其放入一个文件中, 通过带有 -f 选项的 awk
命令来运行.
awk 的数据只有两种类型: 数值与由字符组成的字符串. 文件 emp.data 是很典型的待处理数据,
它既含有单词, 也包括数值, 且字段之间通过制表符或空格分隔.
awk 从它的输入中每次读取一行, 将行分解为一个个的字段 (默认将字段看作是非空白字符组成的
序列). 当前输入行的第一个字段叫作 $1, 第二个是 $2, 依次类推. 一整行记为 $0. 每行的字段数有可能
不一样.
通常情况下, 我们需要做的是打印每一行的部分或全部字段, 也可能会做一些计算. 这一节中的所有
程序都是这种形式.

打印每一行

如果一个动作没有模式, 对于每一个输入行, 该动作都会被执行. 语句 print 会打印每一个当前输
入行, 所以程序
{ print }
会将它所有的输入打印到标准输出. 因为 $0 表示一整行, 所以程序
{ print $0 }
完成同样的工作.

打印某些字段

在一个 print 语句中可以将多个条目打印到同一个输出行中. 打印每一个输入行的第 1 与第 3 个
字段的程序是
{ print $1, $3 }
当 emp.data 作为输入时, 它会输出
Beth 0
Dan 0
Kathy 10
Mark 20
Mary 22
Susie 18
在 print 语句中由逗号分隔的表达式, 在输出时默认用一个空格符分隔.

由 print 打印的每一行都由一个换行符终止. 这些默认行为都可以修改,我们在后续会有介绍.

NF,字段的数量

有时候, 必须总是通过 $1, $2 这样的形式引用字段, 但是任何表达式都可以出现在 $ 的后面, 用来指明一个字段的编号: 表达式被求值, 求出的值被当作字段的编号. Awk 计算当前输入行的字段数量, 并
将它存储在一个内建的变量中, 这个变量叫作 NF. 因此程序
{ print NF, $1, $NF }
将会打印每一个输入行的字段数量, 第一个字段, 以及最后一个字段.

计算和打印

也可以用字段的值进行计算, 并将计算得到的结果放在输出语句中. 程序
{ print $1, $2 * $3 }
是一个很典型的例子, 它会打印雇员的名字与报酬 (每小时工资乘以工作时长):

Beth 0
Dan 0
Kathy 40
Mark 100
Mary 121
Susie 76.5
我们待会儿就会展示如何将输出做得更好看.

打印行号

Awk 提供了另一个内建变量 NR, 这个变量计算到目前为止, 读取到的行的数量. 我们可以使用 NR
和 $0 为 emp.data 的每一行加上行号:

{ print NR, $0 }
输出就像这样:
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

将文本放入输出中

将文本放入输出中
也可以把单词放在字段与算术表达式之间:
{ print “total pay for”, $1, “is”, $2 * $3 }
输出
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 语句.

字段排序

printf 语句具有形式
printf(format, value 1 , value 2 , … , value n )

format 是一个字符串, 它包含按字面打印的文本, 中间散布着格式说明符, 格式说明符用于说明如何打
印值. 一个格式说明符是一个 %, 后面跟着几个字符, 这些字符控制一个 value 的输出格式. 第一个格式
说明符说明 value 1 的输出格式, 第二个格式说明符说明 value 2 的输出格式, 依次类推. 于是, 格式说明
符的数量应该和被打印的 value 一样多.
这个程序使用 printf 打印每位雇员的报酬:

{ printf(“total pay for %s is \$%.2f\n”, \$1, \$2 * $3) }

这个 printf 语句的格式字符串包含两个格式说明符. 第一个格式说明符 %s, 是说将第一个值 $1, 以字 8
符串的形式打印; 第二个格式说明符 %.2f, 是说将第二个值 $2*$3, 按照数值格式打印, 且带有两位小
数. 格式字符串的其他内容 (包括美元符) 按照字面值打印; 字符串末尾的 \n 表示换行符, 该符号使后面
的输出从下一行开始. 当 emp.data 作为输入时, 这个程序输出:

total pay for Beth is $0.00
total pay for Dan is $0.00
total pay for Kathy is $40.00
total pay for Mark is $100.00
total pay for Mary is $121.00
total pay for Susie is $76.50

使用 printf 不会自动产生空格符或换行符; 用户必须自己创建它们, 不要忘了 \n.
另外一个程序打印每位雇员的名字与报酬:
{ printf(“%-8s $%6.2f\n”, $1, \$2 * $3) }

第一个格式说明符 %-8s, 将名字左对齐输出, 占用 8 个字符的宽度. 第二个格式说明符 %6.2f, 将报酬
以带有两位小数的数值格式打印出来, 数字至少占用 6 个字符的宽度:
Beth $ 0.00
Dan $ 0.00
Kathy $ 40.00
Mark $100.00
Mary $121.00
Susie $ 76.50
更多的关于 printf 的例子会慢慢加以介绍

输出排序

设想一下你想要为每一位雇员打印所有的数据, 包括他的报酬, 报酬按照升序排列. 最简单的办法是使用 awk 在每一位雇员的记录前加上报酬, 然后再通过一个排序程序进行排序, 在 Unix 中, 命令行

1
2
3
4
5
6
7
# awk '{ printf("%6.2f %s\n", 2 * 3, $0) }' emp.data | sort -n
0.00 Beth 4.00 0
0.00 Dan 3.75 0
40.00 Kathy 4.00 10
76.50 Susie 4.25 18
100.00 Mark 5.00 20
121.00 Mary 5.50 22

将 awk 的输出通过管道传递给 sort 命令, 最后输出:.

注:The AWK Programming Language学习笔记

-------------本文结束感谢您的阅读-------------

本文标题:awk(1)-快速入门(上)

文章作者:clay

发布时间:2018年07月27日 - 18:07

最后更新:2019年01月28日 - 21:01

原始链接:https://clay-wangzhi.github.io/2018/07/27/awk(1)/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。