Awk 有几种变体:最初的 awk 于 1977 年在 AT&T 贝尔实验室编写,还有一些重新实现的版本,例如 mawk、nawk 以及大多数 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 脚本。如果您想尝试比 print 和 printf 更多的函数,请参阅在线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 的一集。
1 条评论