Awk 是无处不在的 Unix 命令,用于扫描和处理包含可预测模式的文本。然而,由于它具有函数功能,因此也被合理地称为一种编程语言。
令人困惑的是,awk 不止一个。(或者,如果您认为只能有一个,那么就有几个克隆。)有 awk,这是由 Aho、Weinberger 和 Kernighan 编写的原始程序,然后还有 nawk、mawk 和 GNU 版本 gawk。GNU 版本的 awk 是一个高度可移植的免费软件实用程序版本,具有多个独特的功能,因此本文是关于 GNU awk 的。
虽然它的官方名称是 gawk,但在 GNU+Linux 系统上,它被别名为 awk,并作为该命令的默认版本。在其他未附带 GNU awk 的系统上,您必须安装它并将其称为 gawk,而不是 awk。本文互换使用术语 awk 和 gawk。
awk 既是命令又是编程语言,使其成为执行原本可能留给 sort、cut、uniq 和其他常用实用程序任务的强大工具。幸运的是,开源中有很多冗余空间,因此,如果您面临是否使用 awk 的问题,答案可能是一个坚定的“也许”。
awk 灵活性的美妙之处在于,如果您已经决定使用 awk 执行某项任务,那么无论过程中出现什么情况,您都可能可以继续使用 awk。这包括对数据进行排序以使其顺序与交付给您的顺序不同的永恒需求。
示例集
在探索 awk 的排序方法之前,生成一个示例数据集来使用。保持简单,这样您就不会因边缘情况和意外的复杂性而分心。这是本文使用的示例集
Aptenodytes;forsteri;Miller,JF;1778;Emperor
Pygoscelis;papua;Wagler;1832;Gentoo
Eudyptula;minor;Bonaparte;1867;Little Blue
Spheniscus;demersus;Brisson;1760;African
Megadyptes;antipodes;Milne-Edwards;1880;Yellow-eyed
Eudyptes;chrysocome;Viellot;1816;Sothern Rockhopper
Torvaldis;linux;Ewing,L;1996;Tux
这是一个小型数据集,但它提供了各种各样的数据类型
- 属和种名,它们彼此关联,但被认为是独立的
- 姓氏,有时在逗号后带有名字首字母
- 表示日期的整数
- 任意术语
- 所有字段都用分号分隔
根据您的教育背景,您可能会认为这是一个二维数组或表格,或者只是一个以行分隔的数据集合。您如何看待它取决于您自己,因为 awk 对文本的要求仅此而已。如何告诉 awk 您想要解析它取决于您自己。
排序技巧
如果您只想按特定的、可定义的字段(想想电子表格中的“单元格”)对文本数据集进行排序,那么您可以使用 sort 命令。
字段和记录
无论您的输入格式如何,您都必须在其中找到模式,以便您可以专注于对您重要的数据部分。在此示例中,数据由两个因素分隔:行和字段。每新行代表一个新记录,就像您在电子表格或数据库转储中看到的那样。在每行中,都有由分号 (;) 分隔的不同的字段(将它们视为电子表格中的单元格)。
Awk 一次处理一条记录,因此在构建您将提供给 awk 的指令时,您可以专注于一行。确定您想对一行做什么,然后在下一行以及更多行上测试它(无论是心理上还是使用 awk)。您最终会得到一个关于您的 awk 脚本必须做什么才能为您提供您想要的数据结构的良好假设。
在这种情况下,很容易看出每个字段都用分号分隔。为了简单起见,假设您想按每行的第一个字段对列表进行排序。
在排序之前,您必须能够让 awk 专注于每行的第一个字段,这是第一步。终端中 awk 命令的语法是 awk,后跟相关选项,后跟您的 awk 命令,最后是要处理的数据文件。
$ awk --field-separator=";" '{print $1;}' penguins.list
Aptenodytes
Pygoscelis
Eudyptula
Spheniscus
Megadyptes
Eudyptes
Torvaldis
由于字段分隔符是一个对 Bash shell 具有特殊含义的字符,因此您必须将分号括在引号中或在其前面加上反斜杠。此命令仅用于证明您可以专注于特定字段。您可以尝试使用另一个字段的编号的相同命令来查看数据的另一个“列”的内容
$ awk --field-separator=";" '{print $3;}' penguins.list
Miller,JF
Wagler
Bonaparte
Brisson
Milne-Edwards
Viellot
Ewing,L
尚未排序任何内容,但这打下了良好的基础。
脚本编写
Awk 不仅仅是一个命令;它是一种具有索引、数组和函数的编程语言。这意义重大,因为它意味着您可以获取要排序的字段列表,将该列表存储在内存中,对其进行处理,然后打印结果数据。对于如此复杂的一系列操作,在文本文件中工作更容易,因此创建一个名为 sorter.awk 的新文件并输入以下文本
#!/usr/bin/awk -f
BEGIN {
FS=";";
}
这会将文件建立为执行文件中包含的行的 awk 脚本。
BEGIN 语句是 awk 提供的一个特殊的设置函数,用于只需要发生一次的任务。定义内置变量 FS,它代表字段分隔符,并且与您在 awk 命令中使用 --field-separator 设置的值相同,只需要发生一次,因此它包含在 BEGIN 语句中。
awk 中的数组
您已经知道如何通过使用 $ 符号以及字段编号来收集特定字段的值,但在这种情况下,您需要将其存储在数组中,而不是将其打印到终端。这是通过 awk 数组完成的。关于 awk 数组,重要的是它包含键和值。想象一下关于本文的数组;它看起来像这样:author:"seth",title:"How to sort with awk",length:1200。诸如 author、title 和 length 之类的元素是键,后面的内容是值。
在排序的上下文中,这样做的好处是您可以将任何字段指定为键,将任何记录指定为值,然后使用内置的 awk 函数 asorti()(按索引排序)按键排序。现在,任意假设您只想按第二个字段排序。
未以特殊关键字 BEGIN 或 END 开头的 Awk 语句是循环,这些循环在每个记录处发生。这是脚本中扫描数据以查找模式并相应地处理数据的部分。每次 awk 将注意力转向记录时,都会执行 {} 中的语句(除非以 BEGIN 或 END 开头)。
要向数组添加键和值,请创建一个变量(在本示例脚本中,我将其称为 ARRAY,这不是很原始,但非常清楚)包含一个数组,然后为其分配一个方括号中的键,并使用等号 (=) 分配一个值。
{ # dump each field into an array
ARRAY[$2] = $R;
}
在此语句中,第二个字段 ($2) 的内容用作键项,当前记录 ($R) 用作值。
asorti() 函数
除了数组之外,awk 还有几个基本函数,您可以将它们用作常见任务的快速简便的解决方案。GNU awk 中引入的函数之一 asorti() 提供了按键(或索引)或值对数组进行排序的功能。
您只能在数组填充后对其进行排序,这意味着此操作不得与每个新记录一起发生,而只能在脚本的最后阶段发生。为此,awk 提供了特殊的 END 关键字。END 语句与 BEGIN 相反,它只发生一次,并且仅在扫描完所有记录后发生。
将其添加到您的脚本
END {
asorti(ARRAY,SARRAY);
# get length
j = length(SARRAY);
for (i = 1; i <= j; i++) {
printf("%s %s\n", SARRAY[i],ARRAY[SARRAY[i]])
}
}
asorti() 函数获取 ARRAY 的内容,按索引对其进行排序,并将结果放在名为 SARRAY 的新数组中(我在本文中发明的任意名称,意思是已排序的数组)。
接下来,变量 j(另一个任意名称)被分配 length() 函数的结果,该函数计算 SARRAY 中的项目数。
最后,使用 for 循环遍历 SARRAY 中的每个项目,使用 printf() 函数打印每个键,后跟 ARRAY 中该键的对应值。
运行脚本
要运行您的 awk 脚本,使其可执行
$ chmod +x sorter.awk
然后针对 penguin.list 示例数据运行它
$ ./sorter.awk penguins.list
antipodes Megadyptes;antipodes;Milne-Edwards;1880;Yellow-eyed
chrysocome Eudyptes;chrysocome;Viellot;1816;Sothern Rockhopper
demersus Spheniscus;demersus;Brisson;1760;African
forsteri Aptenodytes;forsteri;Miller,JF;1778;Emperor
linux Torvaldis;linux;Ewing,L;1996;Tux
minor Eudyptula;minor;Bonaparte;1867;Little Blue
papua Pygoscelis;papua;Wagler;1832;Gentoo
如您所见,数据按第二个字段排序。
这有点限制性。最好能够灵活地在运行时选择要用作排序键的字段,这样您就可以在任何数据集上使用此脚本并获得有意义的结果。
添加命令选项
您可以通过在脚本中使用文字值 var 将命令变量添加到 awk 脚本。更改您的脚本,以便您的迭代子句在创建数组时使用 var
{ # dump each field into an array
ARRAY[$var] = $R;
}
尝试运行脚本,使其通过在使用 -v var 选项执行时按第三个字段排序
$ ./sorter.awk -v var=3 penguins.list
Bonaparte Eudyptula;minor;Bonaparte;1867;Little Blue
Brisson Spheniscus;demersus;Brisson;1760;African
Ewing,L Torvaldis;linux;Ewing,L;1996;Tux
Miller,JF Aptenodytes;forsteri;Miller,JF;1778;Emperor
Milne-Edwards Megadyptes;antipodes;Milne-Edwards;1880;Yellow-eyed
Viellot Eudyptes;chrysocome;Viellot;1816;Sothern Rockhopper
Wagler Pygoscelis;papua;Wagler;1832;Gentoo
修复
本文演示了如何在纯 GNU awk 中对数据进行排序。可以改进该脚本,因此,如果它对您有用,请花一些时间研究 gawk 手册页上的 awk 函数,并自定义脚本以获得更好的输出。
这是到目前为止的完整脚本
#!/usr/bin/awk -f
# GPLv3 appears here
# usage: ./sorter.awk -v var=NUM FILE
BEGIN { FS=";"; }
{ # dump each field into an array
ARRAY[$var] = $R;
}
END {
asorti(ARRAY,SARRAY);
# get length
j = length(SARRAY);
for (i = 1; i <= j; i++) {
printf("%s %s\n", SARRAY[i],ARRAY[SARRAY[i]])
}
}
4 条评论