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
这是一个小型数据集,但它提供了各种各样的数据类型
- 属和种名,它们彼此关联但被认为是独立的
- 姓氏,有时在逗号后带有名字首字母
- 表示日期的整数
- 任意术语
- 所有字段均以分号分隔
根据您的教育背景,您可以将其视为 2D 数组或表格,或者只是按行分隔的数据集合。您如何看待它取决于您,因为 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(代表字段分隔符,它与您在使用 --field-separator 的 awk 命令中设置的值相同)只需要发生一次,因此它包含在 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 关键字。BEGIN 的反义词,END 语句仅发生一次,并且仅在扫描完所有记录后发生。
将其添加到您的脚本
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 的新数组中(我在本文中发明的任意名称,意思是已排序的 ARRAY)。
接下来,变量 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 条评论