简介

在命令行中使用 Awk 处理文本文件非常简单。例如,假设我有一个名为 "coins.txt" 的文件,该文件描述了一组硬币的相关信息。文件中的每一行包含以下内容:

  • metal(金属种类)
  • weight in ounces(重量,单位:盎司)
  • date minted(铸造年份)
  • country of origin(原产国)
  • description(描述)

该文件的内容如下:

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

打印硬币描述

有批评者可能会说:“这很简单,任何 grepfind 工具都能做到。” 这的确没错,但 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 逐行扫描输入文件,寻找符合 search pattern(搜索模式) 的行,并对这些行执行指定的 program actions(程序操作)。在上面的例子中,具体的操作是:

{print $5,$6,$7,$8}

这里,print 语句的作用显而易见:它用于输出特定字段的值。而 $5, $6, $7, $8 是字段变量(field variables),它们按照行中的单词顺序存储文本内容。例如:

  • $1 代表当前行的第 1 个单词
  • $2 代表第 2 个单词
  • $3 代表第 3 个单词
  • 依此类推……

在 Awk 默认设置下,单词(字段)是由 空格制表符 分隔的。

根据 "coins.txt" 文件的结构(见上文),可以将字段变量与文件的每一行内容匹配如下:

字段 Awk 变量
金属种类 $1
重量(盎司) $2
铸造年份 $3
原产国 $4
描述信息 $5 ~ $8

通过 Awk,可以灵活地提取特定字段,并对其进行格式化、筛选、统计等各种处理,使其比 grep 等简单的文本搜索工具更加强大和实用。

程序操作

在上述示例中,程序的操作部分({print $5,$6,$7,$8})用于打印包含硬币描述的字段。由于文件中的描述字段可能包含 1 到 4 个字段,但这并不会影响结果,因为 print 语句会自动忽略未定义的字段。

细心的读者可能已经注意到,coins.txt 文件的格式非常整齐,唯一包含多个字段的信息始终位于行尾。如果需要突破这个限制,可以修改字段分隔符,后续将会介绍如何更改。

Awk 默认的行为

Awk 默认的程序操作是打印整行,这意味着以下三个命令是等效的:

awk '/gold/' coins.txt
awk '/gold/ {print}'
awk '/gold/ {print $0}'

值得注意的是,Awk 识别 $0 变量,它表示整行文本。尽管 $0 在这里是冗余的,但它能够让程序的操作更加清晰。


条件判断

假设我想列出所有 1980 年之前铸造 的硬币,可以使用以下 Awk 命令:

awk '{if ($3 < 1980) print $3, "    ",$5,$6,$7,$8}' coins.txt

执行后输出:

1908    Franz Josef 100 Korona
1979    Krugerrand

在这个新示例中,引入了一些新的概念:

1. 打印匹配的行

如果 未指定搜索模式,Awk 默认匹配输入文件中的所有行,并对每一行执行指定的操作。

2. 自定义输出

print 语句可以输出自定义文本。在这里,我们在输出的年份和描述字段之间插入了 四个空格

print $3, "    ",$5,$6,$7,$8

其中," " 代表四个空格。

3. 使用 if 语句

if 语句用于检查特定的条件,只有当条件满足时,print 语句才会执行:

if ($3 < 1980)

这里的条件 ($3 < 1980) 表示 如果第三个字段(年份)小于 1980,则执行 print 语句。

4. Awk 的数据类型

在大多数编程语言中,字符串和数字是不同的数据类型,它们的操作方式各不相同:

  • 数字可以进行加法、减法等数学运算。
  • 字符串可以进行拼接,但不能直接进行数学运算。

然而,在 Awk 中,字符串和数字之间的界限是模糊的。Awk 并不是“强类型”(strongly-typed)语言,它会自动将字符串转换为数值(如果可能的话)。因此,我们可以直接在 $3(年份)字段上执行 数值比较,而不需要显式转换。


BEGIN 和 END

下面的示例统计文件中硬币的总数量

awk 'END {print NR,"coins"}' coins.txt

输出:

13 coins

END 语句

END 语句用于执行文件处理完成后的操作。为了更好地理解 END,需要介绍 Awk 程序的通用结构

awk 'BEGIN { 初始化 } 搜索模式 1 { 操作 } 搜索模式 2 { 操作 } ... END { 结束操作 }' 输入文件
  • BEGIN {}:在 Awk 读取文件之前执行初始化操作(可选)。
  • 搜索模式 { 操作 }:处理匹配的行(可有多个)。
  • END {}:在 Awk 处理完所有行之后执行的操作(可选)。

在示例 awk 'END {print NR,"coins"}' coins.txt 中:

  • NR 是 Awk 的一个内置变量,表示当前处理的行数(即记录数)。
  • 由于 NR 代表文件中的总行数,因此 END {print NR,"coins"} 在文件读取完毕后执行,并打印总行数 + "coins"

Awk 内置变量

  • NR:当前处理的行号(总行数)。
  • NF:当前行的字段数。

计算金币总价值

假设当前金价为 $425/盎司,我们希望计算金币的总价值:

awk '/gold/ {ounces += $2} END {print "value = $" 425*ounces}' coins.txt

输出:

value = $2592.5

变量 ounces

在这个例子中:

{ounces += $2}
  • ounces用户定义的变量(不需要提前声明)。
  • += 运算符用于累加金属的重量(盎司)。
  • 该操作遍历所有包含 "gold" 的行,并将 $2(重量)累加到 ounces 变量中。

C 语言 中,+= 是一种简写

ounces = ounces + $2

计算金币总价值

END {print "value = $" 425*ounces}
  • print 语句的两个参数 "value = $"425*ounces 之间没有逗号,因此它们会直接拼接,不添加额外的空格。

练习

如果不提供输入文件,Awk 允许用户手动输入数据,按 CTRL-D 退出。例如:

awk '{print "You entered:", $0}'

然后手动输入文本并查看输出。

练习题

  1. 修改上述程序,分别计算金币和银币的总重量(盎司)

    • 你需要使用两个模式 /gold//silver/,并定义两个累加变量
  2. 编写 Awk 程序,计算所有在 USA 铸造的硬币的平均重量

    • 你需要统计符合条件的硬币数量,并计算平均值。
  3. 编写 Awk 程序,给每一行文本添加行号

    • 例如,输入:
      Hello World
      This is Awk
      
      输出:
      1: Hello World
      2: This is Awk
      

在下一章,我们将学习如何编写多行 Awk 程序,从而实现更复杂的功能。

Last modified: Thursday, 30 January 2025, 12:32 AM