Awk 入门/Awk 命令行示例
在命令行中使用 Awk 对文本文件执行简单操作非常容易。假设我有一个名为 "coins.txt" 的文件,其中描述了我的硬币收藏。该文件中的每一行都包含以下信息
- 金属
- 重量(盎司)
- 铸造日期
- 原产国
- 描述
该文件的内容如下
gold 1 1986 USA American Eagle gold 1 1908 Austria-Hungary Franz Josef 100 Korona silver 10 1981 USA ingot gold 1 1984 Switzerland ingot gold 1 1979 RSA Krugerrand gold 0.5 1981 RSA Krugerrand gold 0.1 1986 PRC Panda silver 1 1986 USA Liberty dollar gold 0.25 1986 USA Liberty 5-dollar piece silver 0.5 1986 USA Liberty 50-cent piece silver 1 1987 USA Constitution dollar gold 0.25 1987 USA Constitution 5-dollar piece gold 1 1988 Canada Maple Leaf
然后,我可以调用 Awk 列出所有金质硬币,如下所示
awk '/gold/' coins.txt
这告诉 Awk 在文件中搜索包含字符串 "gold" 的文本行,并将其打印出来。结果是
gold 1 1986 USA American Eagle gold 1 1908 Austria-Hungary Franz Josef 100 Korona gold 1 1984 Switzerland ingot gold 1 1979 RSA Krugerrand gold 0.5 1981 RSA Krugerrand gold 0.1 1986 PRC Panda gold 0.25 1986 USA Liberty 5-dollar piece gold 0.25 1987 USA Constitution 5-dollar piece gold 1 1988 Canada Maple Leaf
一个评论家可能会说,这很好,但是任何 "grep" 或 "find" 工具都可以做到同样的事情。没错,但是 Awk 能够做到更多。例如,假设我只想打印描述字段,而省略所有其他文本。那么我可以更改对 Awk 的调用,如下所示
awk '/gold/ {print $5,$6,$7,$8}' coins.txt
这将产生
American Eagle Franz Josef 100 Korona ingot Krugerrand Krugerrand Panda Liberty 5-dollar piece Constitution 5-dollar piece Maple Leaf
- 最简单的 Awk 程序
此示例演示了 Awk 程序最简单的通用形式
awk search pattern { program actions }
Awk 按行扫描输入文件,寻找搜索模式。对于找到的每一行,Awk 都会执行指定的动作。在这个示例中,动作被指定为
{print $5,$6,$7,$8}
print
语句的用途很明显。$5
、$6
、$7
和 $8
是字段或“字段变量”,它们按数字顺序存储每行文本中的词语。例如,$1
存储该行中的第一个词,$2
存储第二个词,依此类推。默认情况下,“词语”或记录被定义为任何由空格分隔的打印字符序列。
基于 "coins.txt" 的结构(见上文),字段变量与文件中每行文本的匹配方式如下
metal: $1 weight: $2 date: $3 country: $4 description: $5 through $8
本示例中的程序动作打印包含描述的字段。文件中的描述字段实际上可能包含一个到四个字段,但这并不重要,因为 "print" 只是忽略任何未定义的字段。细心的读者会注意到 "coins.txt" 文件井然有序,所以唯一包含多个字段的信息位于行的末尾。通过更改字段分隔符(后面会解释)可以克服这个限制。
Awk 的默认程序动作是打印整行,这正是 "print" 在没有参数调用时所做的。这意味着这三个示例是相同的
awk '/gold/' awk '/gold/ {print}' awk '/gold/ {print $0}'
请注意,Awk 将字段变量 $0
识别为表示整行。这有点多余,但它确实有使其操作更明显的优点。
现在假设我想列出所有在 1980 年之前铸造的硬币。我调用 Awk 如下所示
awk '{if ($3 < 1980) print $3, " ",$5,$6,$7,$8}' coins.txt
这将产生
1908 Franz Josef 100 Korona 1979 Krugerrand
这个新的示例添加了一些新的概念
- 打印行
- 如果未指定搜索模式,Awk 将匹配输入文件中的所有行,并在每一行上执行操作。
print
语句可以通过将文本包含在引号中并将其添加到参数列表中来显示自定义文本(在本例中为四个空格)。if
语句用于检查特定条件,只有在该条件为真时才会执行print
语句。
然而,这里有一个微妙的问题。在大多数计算机语言中,字符串是字符串,数字是数字。有一些操作是针对它们中的每一种的,必须使用转换函数将一种专门转换为另一种。你不会连接数字,也不会对字符串执行算术运算。
另一方面,Awk 并没有在字符串和数字之间进行严格区分。用计算机科学术语来说,它不是一种“强类型”语言。Awk 中的所有数据都被视为字符串,但如果该字符串恰好表示一个数字,则可以对其执行数值运算。因此,我们可以对日期字段进行算术比较。
下一个示例打印出收藏中硬币的数量
awk 'END {print NR,"coins"}' coins.txt
这将产生
13 coins
本示例中第一个新项是 END
语句。为了解释这一点,我必须扩展 Awk 程序的通用形式。
- Awk 程序
每个 Awk 程序都遵循以下格式(每个部分都是可选的)
awk 'BEGIN { initializations } search pattern 1 { program actions } search pattern 2 { program actions } ... END { final actions }' input file
BEGIN
子句在 Awk 开始扫描输入文件之前执行任何所需的初始化操作。Awk 程序的后续主体由一系列搜索模式组成,每个模式都有自己的程序动作。Awk 扫描输入文件中的每一行以匹配每个搜索模式,并对找到的每个字符串执行相应的动作。扫描完文件后,可以使用 END
子句执行所需的任何最终动作。
因此,这个示例不会对输入行本身执行任何处理。它所做的只是扫描文件并执行一个最终动作:打印文件中的行数,该行数由 NR
变量给出。NR
代表“记录数”。NR
是 Awk 的“预定义”变量之一。还有其他变量,例如变量 NF
给出了该行中的字段数,但详细解释要等到以后。
假设黄金的当前价格为每盎司 425 美元,我想计算出硬币收藏中金质硬币的总价值(近似值)。我调用 Awk 如下所示
awk '/gold/ {ounces += $2} END {print "value = $" 425*ounces}' coins.txt
这将产生
value = $2592.5
在本示例中,ounces
是我自己定义的变量,或“用户定义”变量。在 Awk 中,几乎任何字符序列都可以用作变量名,只要该名称不与 Awk 中具有特定含义的某些字符串冲突,例如 print
或 NR
或 END
。无需声明变量或初始化变量。作为字符串值处理的变量将被初始化为“空字符串”,这意味着如果你尝试打印它,将没有任何内容。作为数值处理的变量将被初始化为零。
因此,程序动作
{ounces += $2}
将每行匹配的硬币的重量累加到变量 ounces
中。那些用 C 编程的人应该熟悉 +=
运算符。那些不熟悉的人可以放心,这只是以下语句的简写形式
{ounces = ounces + $2}
最终的动作是计算并打印黄金的价值
END {print "value = $" 425*ounces}
这里唯一有趣的是两个打印参数——文字 value = $
和表达式 425*ounces
——用空格分隔,而不是用逗号分隔。这将两个参数在输出时连接在一起,没有任何空格。
- 尝试修改上面的某个程序来分别计算并显示金银的总量(以盎司为单位),但使用同一个程序。你将不得不使用两对模式/动作。
- 编写一个 Awk 程序,查找在美国铸造的所有硬币的平均重量。
- 编写一个 Awk 程序,在每行文本之前重新打印其输入内容,并在其前面加上行号。
在下一章中,我们将学习如何编写长度超过一行的 Awk 程序。