awk 中的字段、记录和变量

在本 awk 系列介绍文章的第二篇中,了解字段、记录和一些强大的 awk 变量。
147 位读者喜欢这篇文章。

Awk 有几种变体:最初的 awk 于 1977 年在 AT&T 贝尔实验室编写,还有一些重新实现的版本,例如 mawknawk 以及大多数 Linux 发行版附带的版本 GNU awk 或 gawk。在大多数 Linux 发行版中,awk 和 gawk 是指 GNU awk 的同义词,键入其中任何一个都会调用相同的 awk 命令。有关 awk 和 gawk 的完整历史记录,请参阅 GNU awk 用户指南

本系列中的第一篇文章表明,awk 在命令行中以以下语法调用

$ awk [options] 'pattern {action}' inputfile

Awk 是命令,它可以接受选项(例如 -F 来定义字段分隔符)。您希望 awk 执行的操作包含在单引号中,至少在终端中发出时是这样。为了进一步强调 awk 命令的哪个部分是您希望它执行的操作,您可以在程序前面加上 -e 选项(但这不是必需的)

$ awk -F, -e '{print $2;}' colours.txt
yellow
blue
green
[...]

记录和字段

Awk 将其输入数据视为一系列记录,这些记录通常是以换行符分隔的行。换句话说,awk 通常将文本文件中的每一行视为一个新记录。每个记录包含一系列字段。字段是由字段分隔符分隔的记录的组成部分。

默认情况下,awk 将空格、制表符和换行符等空白字符视为新字段的指示符。具体来说,awk 将多个空格分隔符视为一个,因此此行包含两个字段

raspberry red

这行也是如此

tuxedo                  black

其他分隔符并非以这种方式处理。假设字段分隔符是逗号,则以下示例记录包含三个字段,其中一个字段可能为零字符长(假设该字段中没有隐藏不可打印的字符)

a,,b

awk 程序

awk 命令的程序部分由一系列规则组成。通常,每个规则都从程序中的新行开始(尽管这不是强制性的)。每个规则都包含一个模式和一个或多个操作

pattern { action }

在规则中,您可以将模式定义为条件,以控制操作是否将在记录上运行。模式可以是简单的比较、正则表达式、两者的组合等等。

例如,这将在记录包含单词“raspberry”时打印该记录

$ awk '/raspberry/ { print $0 }' colours.txt
raspberry red 99

如果没有限定模式,则操作将应用于每个记录。

此外,规则可以仅由模式组成,在这种情况下,将写入整个记录,就好像操作为 { print } 一样。

Awk 程序本质上是数据驱动的,因为操作取决于数据,因此它们与许多其他编程语言中的程序有很大不同。

NF 变量

每个字段都有一个变量作为指定,但字段和记录也有特殊变量。变量 NF 存储 awk 在当前记录中找到的字段数。这可以打印或在测试中使用。这是一个使用上一篇文章中的文本文件的示例

$ awk '{ print $0 " (" NF ")" }' colours.txt
name       color  amount (3)
apple      red    4 (3)
banana     yellow 6 (3)
[...]

Awk 的 print 函数接受一系列参数(可以是变量或字符串)并将它们连接在一起。这就是为什么在本示例中,awk 在每行末尾打印字段数,并用括号括起来作为整数。

NR 变量

除了计算每个记录中的字段数之外,awk 还计算输入记录。记录号保存在变量 NR 中,它可以像任何其他变量一样使用。例如,在每行之前打印记录号

$ awk '{ print NR ": " $0 }' colours.txt
1: name       color  amount
2: apple      red    4
3: banana     yellow 6
4: raspberry  red    3
5: grape      purple 10
[...]

请注意,可以编写此命令,除了 print 之后的一个空格外,没有其他空格,尽管人类更难解析

$ awk '{print NR": "$0}' colours.txt

printf() 函数

为了在输出格式方面具有更大的灵活性,您可以使用 awk printf() 函数。这类似于 C、Lua、Bash 和其他语言中的 printf。它接受一个格式参数,后跟一个逗号分隔的项目列表。参数列表可以用括号括起来。

$ printf format, item1, item2, ...

格式参数(或格式字符串)定义了如何输出其他每个参数。它使用格式说明符来执行此操作,包括 %s 输出字符串和 %d 输出十进制数。以下 printf 语句输出记录,后跟括号中的字段数

$ awk 'printf "%s (%d)\n",$0,NF}' colours.txt
name       color  amount (3)
raspberry  red    4 (3)
banana     yellow 6 (3)
[...]

在此示例中,%s (%d) 提供了每行的结构,而 $0,NF 定义了要插入到 %s%d 位置的数据。请注意,与 print 函数不同,如果没有明确的指令,则不会生成换行符。转义序列 \n 执行此操作。

Awk 脚本

本文中的所有 awk 代码均已在交互式 Bash 提示符下编写和执行。对于更复杂的程序,通常更容易将命令放入文件或脚本中。可以使用选项 -f FILE (不要与 -F 混淆,后者表示字段分隔符)来调用包含程序的文件。

例如,这是一个简单的 awk 脚本。创建一个名为 example1.awk 的文件,其中包含以下内容

/^a/ {print "A: " $0}
/^b/ {print "B: " $0}

按照惯例,此类文件应使用扩展名 .awk,以清楚地表明它们包含 awk 程序。此命名不是强制性的,但它为文件管理器和编辑器(以及您)提供了有关文件用途的有用线索。

运行脚本

$ awk -f example1.awk colours.txt
A: raspberry  red    4
B: banana     yellow 6
A: apple      green  8

通过在顶部添加 #! 行并使其可执行,可以将包含 awk 指令的文件制成脚本。创建一个名为 example2.awk 的文件,其中包含以下内容

#!/usr/bin/awk -f
#
# Print all but line 1 with the line number on the front
#

NR > 1 { 
    printf "%d: %s\n",NR,$0 
}

可以说,脚本中只有一行代码没有任何优势,但有时执行脚本比记住和键入哪怕是一行代码更容易。脚本文件还提供了一个很好的机会来记录命令的作用。以 # 符号开头的行是注释,awk 会忽略它们。

授予文件可执行权限

$ chmod u+x example2.awk

运行脚本

$ ./example2.awk colours.txt
2: apple      red    4
2: banana     yellow 6
4: raspberry red    3
5: grape      purple 10
[...]

将 awk 指令放入脚本文件中的一个优点是,它更易于格式化和编辑。虽然您可以在终端中的单行上编写 awk,但当它跨越多行时,可能会变得难以承受。

试试看

您现在已经充分了解 awk 如何处理您的指令,从而能够编写复杂的 awk 程序。尝试编写一个包含多个规则和至少一个条件模式的 awk 脚本。如果您想尝试比 printprintf 更多的函数,请参阅在线gawk 手册

这是一个帮助您入门的想法

#!/usr/bin/awk -f
#
# Print each record EXCEPT
# IF the first record contains "raspberry", 
# THEN replace "red" with "pi"

$1 == "raspberry" {
	gsub(/red/,"pi")
}

{ print } 

尝试运行此脚本以查看其功能,然后尝试编写您自己的脚本。

本系列中的下一篇文章将介绍更多函数,以实现更复杂(和有用!)的脚本。


本文改编自社区技术播客 Hacker Public Radio 的一集。

接下来阅读什么
标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。他曾在电影和计算机行业工作,通常同时从事这两个行业。

1 条评论

我已经期待学习 Awk 多年了。这些教程非常有用,谢谢!

我尝试使用 Python 解析一些文件。但是用 Python 解析文本是一个坏主意!它非常适合 XML 或 JSON,但不适合纯文本!Awk 可以精确定位我正在寻找的内容,提取我需要的内容,并在瞬间将其返回给我。干得漂亮!

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.