首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >提升awk技能的两个教程【译】

提升awk技能的两个教程【译】

作者头像
程序员架构进阶
发布2021-03-05 10:41:15
发布2021-03-05 10:41:15
5.1K00
代码可运行
举报
文章被收录于专栏:架构进阶架构进阶
运行总次数:0
代码可运行

原文: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表示在最后一行处理完成之后执行。在二者之间,块的格式如下:

代码语言:javascript
代码运行次数:0
运行
复制
模式 { 行为语句 }

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

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

代码语言:javascript
代码运行次数:0
运行
复制
function 函数名(参数列表) { 语句 }

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

awk是怎样处理文本流的?

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

  • FS (字段分隔符): 默认情况是任意空格(" "或tab)
  • RS (记录/行分隔符): 默认是新行标记(\n)
  • NF (number of fields,字段数量): 当awk解析一行记录时,NF代表已解析的字段数量
  • $0: 当前记录(行)
  • 1, 2,
  • NR (记录行数): 截至当前awk脚本已解析的记录行数

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

单行awk脚本

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

代码语言:javascript
代码运行次数:0
运行
复制
awk -F":" '{print $1 }' /etc/passwd

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

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

代码语言:javascript
代码运行次数:0
运行
复制
awk 'BEGIN { FS=":" } {print $1 }' /etc/passwd

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

代码语言:javascript
代码运行次数:0
运行
复制
awk 'BEGIN { FS=":" } ! /\/sbin\/nologin/ {print $1 }' /etc/passwd

awk进阶:邮件合并

现在你已经具备了一些awk基础,下面通过一个更加结构化的示例:创建邮件合并,来尝试深入理解awk。

邮件合并使用两个文件,其中一个文件(本示例中称为email_template.txt)包含你想要发送的邮件模板:

代码语言:javascript
代码运行次数:0
运行
复制
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),是你想要发送邮件的那些人(接收人列表),内容如下:

代码语言:javascript
代码运行次数:0
运行
复制
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。

代码语言:javascript
代码运行次数:0
运行
复制
BEGIN {
  FS=",";
  template="email_template.txt";
  output="acceptance";
  getline;
  NR=0;
}

主函数很简洁:对处理的每一行,设置firstname, lastname, email, 和 title变量的值。模板文件逐行读取,sub函数用于替换相应的变量为指定的字符串。然后该行,连同所做的所有变量替换结果,被输出到输出文件。

由于处理的是模板文件和每行的不同输出文件,因此在处理下一条记录之前,需要清理并关闭这些文件的文件句柄。

代码语言:javascript
代码运行次数:0
运行
复制
{
        # 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);
}

完成!使用下面的命令在命令行运行这个脚本:

代码语言:javascript
代码运行次数:0
运行
复制
awk -f mail_merge.awk proposals.csv

代码语言:javascript
代码运行次数:0
运行
复制
awk -f mail_merge.awk < proposals.csv

你将会在当前目录下找到生成的一系列文本文件。

awk进阶: 词频统计

awk的一个最强大的特性是关联数组。大部分编程语言中,数组元素通常是用数字作为索引,但awk中,数组通过一个key字符串来引用。你可以存储上一章proposals.txt文件中的元素,例如,一行记录可以存储为一个单关联数组:

代码语言:javascript
代码运行次数:0
运行
复制
		proposer["firstname"]=$1;
    proposer["lastname"]=$2;
    proposer["email"]=$3;
    proposer["title"]=$4;

这使得文本处理非常简单。一个使用这个概念的简单示例是词频计数器。你可以解析一个文件,提取出每行的单词(忽略标点符号),为该行中的每个单词的计数器递增,然后输出在文本中出现次数在前20的单词。

首先,在wordcount.awk文件中,设置字段分隔符为包含空格和标点符号的正则表达式:

代码语言:javascript
代码运行次数:0
运行
复制
BEGIN {
        # ignore 1 or more consecutive occurrences of the characters
        # in the character group below
        FS="[ .,:;()<>{}@!\"'\t]+";
}

然后,在主循环函数中,遍历每个字段,忽略空字段(当行尾有标点符号时会出现这种情况),并对本行中的每个单词增加单词计数。

代码语言:javascript
代码运行次数:0
运行
复制
{
        for (i = 1; i <= NF; i++) {
                if ($i != "") {
                        words[$i]++;
                }
        }
}

最终,在文本处理完成后,使用END函数来打印words数组内容,利用awk的管道能力输出到shell命令,来执行数字排序,并打印前20个出现次数最高的单词:

代码语言:javascript
代码运行次数:0
运行
复制
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脚本示例。

更多Linux资源:

  • Linux commands cheat sheet
  • Advanced Linux commands cheat sheet
  • Free online course: RHEL Technical Overview
  • Linux networking cheat sheet
  • SELinux cheat sheet
  • Linux common commands cheat sheet
  • What are Linux containers?
  • Our latest Linux articles
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员架构进阶 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • awk的程序结构
  • awk是怎样处理文本流的?
  • 单行awk脚本
  • awk进阶:邮件合并
  • awk进阶: 词频统计
  • 下一步?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档