Awk 是 Unix 和 Linux 用户工具箱中最古老的工具之一。它由 Alfred Aho、Peter Weinberger 和 Brian Kernighan(工具名称中的 A、W 和 K)于 1970 年代创建,用于复杂处理文本流。 它是 sed(流编辑器)的配套工具,sed 专为逐行处理文本文件而设计。 Awk 允许更复杂的结构化程序,并且是一种完整的编程语言。
本文将解释如何使用 awk 来执行更结构化和复杂的任务,包括一个简单的邮件合并应用程序。
Awk 程序结构
awk 脚本由用 {} (花括号) 包围的功能块组成。 有两个特殊的功能块 BEGIN 和 END,它们分别在处理输入流的第一行之前和处理最后一行之后执行。 在两者之间,块的格式为
pattern { action statements }
当输入缓冲区中的行与模式匹配时,每个块都会执行。 如果未包含任何模式,则函数块会在输入流的每一行上执行。
此外,以下语法可用于在 awk 中定义可以从任何块调用的函数
function name(parameter list) { statements }
模式匹配块和函数的这种组合使开发人员可以构建 awk 程序,以提高可重用性和可读性。
Awk 如何处理文本流
Awk 一次从其输入文件或流中读取一行文本,并使用字段分隔符将其解析为多个字段。 在 awk 术语中,当前缓冲区是一个*记录*。 有许多特殊变量会影响 awk 如何读取和处理文件
- FS(字段分隔符):默认情况下,这是任何空格(空格或制表符)
- RS(记录分隔符):默认情况下,是换行符(\n)
- NF(字段数):当 awk 解析一行时,此变量设置为已解析的字段数
- $0: 当前记录
- $1, $2, $3, 等等: 当前记录中的第一个、第二个、第三个等等字段
- NR(记录数):awk 脚本到目前为止已解析的记录数
还有许多其他变量会影响 awk 的行为,但这些足以开始。
Awk 单行命令
对于如此强大的工具,有趣的是 awk 的大多数用法都是基本的单行命令。 也许最常见的 awk 程序是从 CSV 文件、日志文件等的输入行中打印选定的字段。 例如,以下单行命令从 /etc/passwd 打印用户名列表
awk -F":" '{print $1 }' /etc/passwd
如上所述,$1 是当前记录中的第一个字段。 -F 选项将 FS 变量设置为字符 :。
字段分隔符也可以在 BEGIN 函数块中设置
awk 'BEGIN { FS=":" } {print $1 }' /etc/passwd
在以下示例中,可以通过在块前面加上模式匹配来打印其 shell 不是 /sbin/nologin 的每个用户
awk 'BEGIN { FS=":" } ! /\/sbin\/nologin/ {print $1 }' /etc/passwd
高级 awk:邮件合并
现在你已经掌握了一些基础知识,尝试通过一个更结构化的示例来深入研究 awk:创建邮件合并。
邮件合并使用两个文件,一个(在本示例中称为 email_template.txt)包含要发送的电子邮件的模板
From: Program committee <pc@event.org>
To: {firstname} {lastname} <{email}>
Subject: Your presentation proposal
Dear {firstname},
Thank you for your presentation proposal:
{title}
We are pleased to inform you that your proposal has been successful! We
will contact you shortly with further information about the event
schedule.
Thank you,
The Program Committee
另一个是包含要向其发送电子邮件的人员的 CSV 文件(称为 proposals.csv)
firstname,lastname,email,title
Harry,Potter,hpotter@hogwarts.edu,"Defeating your nemesis in 3 easy steps"
Jack,Reacher,reacher@covert.mil,"Hand-to-hand combat for beginners"
Mickey,Mouse,mmouse@disney.com,"Surviving public speaking with a squeaky voice"
Santa,Claus,sclaus@northpole.org,"Efficient list-making"
你想要读取 CSV 文件,替换第一个文件中的相关字段(跳过第一行),然后将结果写入名为 acceptanceN.txt 的文件,为解析的每一行递增 N。
将 awk 程序写入名为 mail_merge.awk 的文件中。 语句在 awk 脚本中用 ; 分隔。 第一项任务是设置字段分隔符变量和脚本需要的其他几个变量。 你还需要读取并丢弃 CSV 中的第一行,否则将创建一个以 *Dear firstname* 开头的文件。 为此,请使用特殊函数 getline 并在读取后将记录计数器重置为 0。
BEGIN {
FS=",";
template="email_template.txt";
output="acceptance";
getline;
NR=0;
}
主函数非常简单:对于处理的每一行,都会为各种字段设置一个变量——firstname、lastname、email 和 title。 逐行读取模板文件,并使用函数 sub 将特殊字符序列的任何出现替换为相关变量的值。 然后将进行任何替换的行输出到输出文件。
由于你正在处理模板文件和每行不同的输出文件,因此需要在处理下一条记录之前清理并关闭这些文件的文件句柄。
{
# Read relevant fields from input file
firstname=$1;
lastname=$2;
email=$3;
title=$4;
# Set output filename
outfile=(output NR ".txt");
# Read a line from template, replace special fields, and
# print result to output file
while ( (getline ln < template) > 0 )
{
sub(/{firstname}/,firstname,ln);
sub(/{lastname}/,lastname,ln);
sub(/{email}/,email,ln);
sub(/{title}/,title,ln);
print(ln) > outfile;
}
# Close template and output file in advance of next record
close(outfile);
close(template);
}
你完成了! 在命令行中使用以下命令运行脚本
awk -f mail_merge.awk proposals.csv
或者
awk -f mail_merge.awk < proposals.csv
你将在当前目录中找到生成的文本文件。
高级 awk:单词频率计数
awk 中最强大的功能之一是关联数组。 在大多数编程语言中,数组条目通常按数字索引,但在 awk 中,数组由键字符串引用。 你可以从上一节中的 *proposals.txt* 文件中存储一个条目。 例如,在单个关联数组中,像这样
proposer["firstname"]=$1;
proposer["lastname"]=$2;
proposer["email"]=$3;
proposer["title"]=$4;
这使得文本处理非常容易。 使用这个概念的一个简单程序是单词频率计数器。 你可以解析一个文件,分解每一行中的单词(忽略标点符号),递增该行中每个单词的计数器,然后输出文本中出现频率最高的前 20 个单词。
首先,在一个名为 wordcount.awk 的文件中,将字段分隔符设置为包含空格和标点符号的正则表达式
BEGIN {
# ignore 1 or more consecutive occurrences of the characters
# in the character group below
FS="[ .,:;()<>{}@!\"'\t]+";
}
接下来,主循环函数将迭代每个字段,忽略任何空字段(如果行尾有标点符号,则会发生这种情况),并递增该行中单词的单词计数。
{
for (i = 1; i <= NF; i++) {
if ($i != "") {
words[$i]++;
}
}
}
最后,在处理完文本后,使用 END 函数打印数组的内容,然后使用 awk 将输出通过管道传输到 shell 命令的功能来执行数字排序并打印 20 个最常出现的单词
END {
sort_head = "sort -k2 -nr | head -n 20";
for (word in words) {
printf "%s\t%d\n", word, words[word] | sort_head;
}
close (sort_head);
}
在本文的早期草稿中运行此脚本会产生以下输出
[dneary@dhcp-49-32.bos.redhat.com]$ awk -f wordcount.awk < awk_article.txt
the 79
awk 41
a 39
and 33
of 32
in 27
to 26
is 25
line 23
for 23
will 22
file 21
we 16
We 15
with 12
which 12
by 12
this 11
output 11
function 11
接下来是什么?
如果你想了解更多关于 awk 编程的信息,我强烈推荐 Dale Dougherty 和 Arnold Robbins 的 Sed and awk 这本书。
掌握“扩展正则表达式”是掌握 awk 编程的关键之一。 Awk 为你可能已经熟悉的 sed 正则表达式语法提供了几个强大的补充。
另一个学习 awk 的好资源是 GNU awk 用户指南。 它有 awk 内置函数库的完整参考,以及大量简单和复杂的 awk 脚本示例。
评论已关闭。