提升 awk 技能的两个教程

原文:https://opensource.com/article/19/10/advanced-awk

作者:Dave Neary

副标题:通过邮件合并和单词计数,超越单行awk脚本

图片来源: Opensource.com



awk是Unix和Linux用户工具箱中最古老的工具之一。由Alfred Aho, Peter Weinberger, and Brian Kernighan (即awk命名中的A, W, K)于20世纪70年代创建,用于文本流的复杂处理。awk是流编辑器sed的配套工具,sed是为逐行处理文本文件而设计的。awk则允许更复杂的结构化编程,是一种完整的编程语言。

本文将阐述如何使用awk来处理更加结构化和更复杂的任务,包含一个简单的邮件合并应用程序。

awk的程序结构

一个awk脚本由通过花括号{}作为边界的函数块组成。有两个特殊的函数块,BEGINEND,BEGIN表示在处理第一行输入流之前执行,而END表示在最后一行处理完成之后执行。在二者之间,块的格式如下:

模式 { 行为语句 }

每个块在当输入缓冲区中的行与模式匹配时执行。如果没有包含任何模式,那么这个函数块将对输入流中的每一行都会执行。

同样,下面的语法可以用于定义awk中的函数,并可以被任意函数块调用

function 函数名(参数列表) { 语句 }

这种模式匹配块和函数的组合,使开发者能够开发结构化的awk程序,具备可重用和提升可读性。

awk是怎样处理文本流的?

awk从输入文件或流中每次读取一行文本,并使用字段分隔符将其解析为多个字段。awk术语中,当前缓冲区(buffer)是一条记录。有许多特殊变量影响着awk如何去读取和处理文件:

  • FS (字段分隔符): 默认情况是任意空格(" "或tab)

  • RS (记录/行分隔符): 默认是新行标记(\n)

  • NF (number of fields,字段数量): 当awk解析一行记录时,NF代表已解析的字段数量

  • $0: 当前记录(行)

  • $1, $2, $3, ...: 当前记录的第一,二,三, ...,个字段

  • NR (记录行数): 截至当前awk脚本已解析的记录行数

除此之外,还有很多其他影响awk行为的变量,但作为开始了解这些已经足够了。

单行awk脚本

对于如此强大的工具,有趣的一点是大部分对awk的使用都是基本的单行代码。也许大部分常见的awk程序都是以csv文件、log文件等作为输入,打印其中的指定字段。例如,下面的单行脚本打印了 /etc/passwd中的用户名列表:

awk -F":" '{print $1 }' /etc/passwd

如我们上面提到的,$1是当前记录的第一个字段。-F选项设置了字段分割变量为冒号 :。

字段分隔符也可以设置到BEGIN函数块中:

awk 'BEGIN { FS=":" } {print $1 }' /etc/passwd

下面的示例中,每个shell不是/sbin/nologin的用户,都可以通过在打印块前增加一个模式匹配来实现:

awk 'BEGIN { FS=":" } ! /\/sbin\/nologin/ {print $1 }' /etc/passwd

awk进阶:邮件合并

现在你已经具备了一些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文件,替换第一个文件中的相应字段(跳过proposals.csv的第一行),然后把结果写入名为acceptanceN.txt的文件中,其中N随着你解析每一行递增。

写出awk程序到mail_merge.awk,awk脚本中的语句通过 ;分隔。第一个任务是设置脚本所需的分割变量及其他变量。你也需要读取并丢弃proposals.csv的第一行,否则会创建出一个以Dear firstname开头的文件。为了做到这点,需要使用特定的函数getline并在读取之后,把记录计数器重置为0。

BEGIN {

FS=",";

  template="email_template.txt";

  output="acceptance";

  getline;

  NR=0;

}

主函数很简洁:对处理的每一行,设置firstnamelastnameemail, 和 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中,数组通过一个key字符串来引用。你可以存储上一章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函数来打印words数组内容,利用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 user guide(GUN awk用户指南). 它包含了完整的awk内置库,同时也提供了大量从简单到复杂的awk脚本示例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值