AWK学习

写在前面

一般语言学习,我们都会先学习其变量,程序控制,数组,基本运算(逻辑运算,关系运算,数学运算,字符串基本操作),函数声明,输入输出等。但是我想先通过一些实例对awk形成感性认识,然后再学习awk的基本语法,最后了解awk语言的优缺点及其适用场景。

awk语言的产生

这部分是话唠的细碎练,不感兴趣者可以跳过。以前上高中数理化的时候,特别不能理解学习一门新知识,为什么还要了解相关理论的发展历史。后来才明白了解历史是为了追本溯源。就如同如若想理解人性,我想去看进化史和动物世界会让你受益颇多。一门技术的产生很大程度上是为了满足某项需求,解决某些问题。awk语言的由来是因为grep和sed只能进行简单模式匹配,并且只能打印整行,但是在很多数据分析中,我只想抽取部分文本,并且希望希望能够进行字段计算。于是乎非常简练的,采用pattern-action model并且擅长进行字段计算的awk语言产生。正如the awk programming language中所说:

        our goal, as we remember it, was to create a pattern-scanning language that would understand fields, one with patterns to match fields and actions to manipulate them. 

awk语言最初是作为一个小工具用于输入数据的抽取和检验,输出数据的重组,作为新程序的输入,以及简单的字段计算和数据统计,生成相应报表。后来由于awk语言的简单方便,使得其被更多人使用,从而需求也更多,所以后来引入了一些内建变量,更多的内建函数(数学运算,字符串操作等),还有函数自定义,输入输出流等。在引入的过程中,会与awk原先要求的简短相冲突,但是awk的开发者对其进行了一定的权衡。

awk工具的常用功能

简单了解了awk工具的由来后,我们通过一些实例来学习如何使用awk工具。

0. 实验的数据user_info

uid, friends,blacklists,等级,性别,年龄,手机号等

123456789       734824924,546788321     123456710       1       male       21      15510382567
123456710       434556788       432423555,734824924     0       female    22      17789564321
734824924       123456789       434556788                          1       male       18      1889898898
434556788       123456710,432423555     546788321       2       male       31      17565438291
432423555       434556788       734824924                          2       female    29      12387654563
546788321       123456789       432423555                          1       male       25      14412349876
123498765       734824924,546788321     123456710       1       male       21      15510382567

1. 数据的抽取和拆分

(1) 按列进行拆分

如sql语句中的select命令一样,有时候我们只需要几列数据. 比如我想知道当前user_info中有哪些uid的相应信息。代码如下,而结果文件uids.txt存储的是第一列的uid。awk中默认用空格分隔每行的各个字段,也可以使用-F定制分隔字符串。同时可以用>重定向输出到指定文件。$0表示一行,$i表示第i列。awk的执行方式是每次读入一行,然后用pattern进行匹配,用action对匹配行进行处理。

如果是想获取用户123456789的好友列表,并且想输出为一列,可以进行如下操作:

上述的pattern,就是uid为123456789,action就是将其好友列表打印出来。上述省略了if,全称应该是if($1==123456789)。在awk中,BEGIN后面大括号的语句是在文本文件读入之前进行的初始化,而END后面的语句是在文本文件读入pattern-action完毕后执行的语句。此外上述用到内置字符串分隔函数split,对于split的用法大家可以问谷哥或者度娘。

DIY1:假设好友列表是一组由英文逗号分隔的uids,并且好友列表的第一个uid是最亲密好友。如果我要打印所有uid和相应的亲密好友。如何实现?

(2) 按行进行拆分

——-按行号进行拆分

对大文件拆分成几个小文件,我们都知道linux有split命令,如何用awk实现文件的拆分?基本逻辑是对行号进行拆分,将相应行读入到各个子文件。此处awk用到了一个是-v变量设置,另一个是用来标记当前行号的内置变量NF。以下是php代码。

上面的拆分是采用类似分糖的方式,假如现在有AB两个小朋友,先给A分一块,再给B分一块,然后又给A分一块,再给B分一块,依次循环,直至糖分发完毕。秉承awk的one-liner,上述的代码也可以一行代码实现。ps: 为了方便理解程序逻辑,特别打印出行号NR。

DIY2:上述拆分的好处在于我们只需要知道要拆分成多少个文件,不需要知道文件总共行数,也可以平均拆分成小文件。假设我们知道每个小文件容忍的最大行数为m,不知道文件总行数。这个时候,我们想将大文件的第一个m行数据拆分到第一个文件,第二个m行数据拆分到第二个文件,第i个m行数据拆分到第i个文件,直至大文件的所有行拆分完毕。使用awk如何进行实现?

——-按内容进行分类

上述是按照行号进行拆分,但是我们很多时候,需要按照内容进行分类。比如我要将user_info按性别分成不同的文件中。可以采用如下代码,就会生成female和male两个文件,分别存储的是性别为女和性别为男的用户信息。

上面的代码是不是很简洁,哈哈。awk尽其所能做到用一行代码来完成一些任务。

2. 数据的检验(正则式的匹配,uid是否都为数字)

有时候我们对程序输入数据要进行事先检验,将不合法的数据检验出来。比如uid一般都是数字。我们就要检验user_info的第一列的uid是否都匹配/^[0-9]+$/的正则表达式。

具体代码如下:

3. 去重排序

(1)单个文件的去重和排序

比如某个用户其有大小号,我们假设使用手机号或者身份证号唯一标识一个用户。所以我们就是要使得最后一列的手机号码唯一。我们假设取某手机号码最后一次出现的uid为其大号。此时如何进行数据去重呢?这个时候我们采用到关联数组。

DIY3:假设我们取手机号码第一次出现相应的uid为其大号,代码应该如何修改呢?

(2)两个文件的差(过滤掉黑名单),交(求公共好友),并(求所有的好友)

以上是将black_list中的黑名单从total_file中过滤掉。得到过滤掉黑名单的uid文件exclude_blacklist_file。其实就是total_file和black_list做差集。

DIY4:如何实现两个文件做交集和并集,比如求两个人的friends列表的交集,即他们的公共好友。比如求两个人的friends圈,即求两个人friends列表的并集。假设friends_uid1文件存储的是uid1的好友列表。friends_uid2文件存储的是uid2的好友列表。两个文件都是用一行一个uid来存储好友列表。

4. 字符串的基本操作(查找,截取,替换)

awk中一般采用正则表达式来进行字符串查找匹配。awk中内置了一系列的字符串处理函数,比如字符串截取函数substr(str_field, start, end), 字符串分隔函数split(str_field, split_arr, delimiter),字符串替换函数gsub(regular_expression, replace_str, str_field), 字符串长度函数length(str_field)等。具体可以参见下面的awk基本语法。

 DIY5: 在user_info表中,为了更好统计数据,用1来表示male,用0来表示female。所以采用gsub来实现上述功能。

5. 简单的分析统计(生成直方图)—年龄分布

假设用户的年龄区间为[0,100],对其按10年进行划分,每一个十年为一个年龄段。统计处于每一个年龄段的用户数,并生成倒置的直方图。每行为年龄段,处于该年龄段的用户数。

输出生成的相应的结果图如下。

 

6. 输入输出流—这个肯定是要的

 

7. awk通过system函数使用系统的shell命令

假设files文件中每一行存储的都是一个文件的路径,我们要统计每一个文件的行数。可以采用以下的代码。

当你只是想简单获取一些文件信息,或者像将shell命令集成到awk中,此法非常奏效。当然也可以采用xargs完成上述同样的任务。条条大道通罗马~~

 

 awk语言的基本语法

0. Pattern-Action Model

awk语言的编写一般是采用pattern-action的形式。awk语言的执行方式是每次读入一行到缓存区,采用pattern来匹配行,如果符合pattern,则采用action对该行进行处理。常用的pattern和action参见下两表。

patterns

actions

1. 常量,变量(内建变量),数组(关联数组)

awk的常量主要有字符串和数字。变量也主要是字符串和数字,可以根据上下文背景,自动转化类型。变量和数组都无需声明。数字变量默认为0,字符串变量默认为空字符串。

所谓关联数组,就是数组为key-value的形式,key可以是字符串,可以是数字,当然也可以是数组(有待考证)。当数组的key为数组的时候,一维数组变为二维数组。value的取值也可以是数字,字符串和数组。当value为数组的时候,一维数组也变成了多维数组。awk的关联数组实际上不支持多维数组,但能使用一维数组来实现多维数组,并且其可以采用a[i,j]来访问二维数组元素。

awk中为了方便进行字段计算,有很多内建变量,常用的有当前行的字段数NF,当前行号NR等,常用的内建变量可以参见下表。

 

除了上表的常用内建变量外,还有ARGIND用于表示awk的参数索引。gawk现在支持更多的内建变量,具体大家可以google it。

DIY 6:使用awk的关联数组打印出九九乘法表,最好能one-liners

2. 正则表达式和运算操作

在awk中可以用正则表达式来匹配一些文本行,从而提取到想要的记录。常用的正则表达式符号可以参见下表。if you ask for more, please google it.

2-3 Regular Expressions

运算操作有关系运算(大于,小于,等于等),数值运算(加减乘除等),逻辑运算(与&&,或||,否!)具体可以参见table2-8

2-8 expression operators

3. 程序控制—-BEGIN, END, if, while, for, next, exit

在前面已经简单提到过BEGIN和END,BEGIN后面大括号的语句是在读取文本数据前进行执行,一般用于初始化工作,而END后面大括号的语句是在读取文本数据完毕后进行执行,一般进行处理结果数据的汇总输出。

if 、for、while语法类似于C,只是awk采用的是关联数组,可以使用in来遍历数组的key。比如if (key1 in a)判断一个key在数组a中是否存在。比如for(key in arr){value=arr[key];print value;}遍历数组。其次在awk中采用next终止对当前行的操作,即直接扫描下一行,对当前行不执行next后面的语句。awk还可以采用exit来终止读取文本。

DIY7:如果对排序算法比较熟悉,为了熟悉对awk程序控制语法。可以尝试使用awk实现插入排序,快速排序和堆排序。我相信that is a piece of cake for you~~

4. 函数声明(自定义函数,内建函数)

为了方便用户更加灵活的使用awk工具,awk的开发者使得awk支持自定义函数。个人看来,awk一般是一行代码,很少使用自定义函数。如果某项功能实现真的需要自定义函数,或许会考虑采用其他语言来进行实现。当然awk支持时候函数自定义,可以方便awk实现更多的功能。

awk除了用户自定义函数以外,还提供很多内建函数。我常用的是字符串内建函数,随机函数,数学运算函数,字符串内建函数具体参见下表:

table2-7 built-in string functions

5. 输出格式化

awk的输出格式化类似于C,对于C的这种格式化输出,由于用得少所以比较薄弱,故还有待加强。

2-10 examples of printf specifications

table 2-10 exmples of print specifications

6. 输入流

 

awk语言的优缺点和适用场景

awk语言的优点在于简短,好用,one-liner。其缺点在于实现的功能有限,性能也有限。故我们需要知道awk的适用场景,利用其在字段计算方面的强项非常方便的处理一些文本数据。在工作中,我发现对一个系统进行运营维护的时候,日志分析起到了非常大的作用。日志一方面可以在debugging,发现定位问题方面跟踪数据流,另一方面可以得到程序运行的状态和各种业务反馈数据,有利于我们对系统和业务流程进行优化。而awk是进行日志分析非常强有力的工具。

对于awk的适用场景,我想使用 the awk programming language中的一段原话来回应。

In other words, break the job into separate pieces, and apply the most appropriate tool to each piece.

翻译成中文,就是将一个任务拆分成一个个小任务,并且采用最合适的工具来逐一攻克相应的问题。如果是要对数据排序,sort命令相对我们前面的数组的方式会更好;如果是要从一个大文件中搜索中小量的数据,grep会比awk中的gsub函数和sed好;如果是要对文件中进行大面积的查找替换,sed会优于awk中的gsub字符串函数。而对于字段操作,awk是不错的选择。虽然在上述实例中有使用awk进行字符串查找替换,那只是为了开阔大家对awk的认识,但是在实际工作中,还是采用适合的工具做其擅长的事情。

我怕我的中文翻译不太好,给出误读,特此附上相应的英文。

throughout this book, we made extensive use of the system sort command, for example, rather than writting our own sort in awk. if you have to search a big file to isolate a small amount of data, use grep or egrep for the searching and awk for the processing. If there are a large number of substitutions (for example, the cross-reference program of Chapter 5), you might use a stream editor like sed for that part.

至于awk的性能,一方面取决于算法,另一方面取决于硬件。我们可以采用更有效地算法,并且采用效率更高的语言来实现awk语句中比较耗时的部分。awk的性能是当文件越大的时候,其速度会越来越慢,当文件足够大的时候,它的运行速度会慢得你无法接受。程序的性能也随机器不同而有很大差异。对于awk语句的优化,我们首先要知道那一部分比较耗时。如the awk programming language中所说:before you can improve the behavior of a program, you need to understand where the time is going.

后记

特别感谢lay小盆友让我见识了awk,后来在gx童鞋的脚本中,认识到awk很好很强大。在日常工作的日志分析和数据统计中也会用到awk,这也加深了我对awk的认识和理解。awk给我的感觉是“短小精悍”。而且相对shell,我觉得awk的语法更简单,可能是由于awk的语法更接近于C的缘故。装B升华下(嘿嘿),awk语言的特点让我想到大道至简,比如做人的道理和做事的方法,核心要点或许也就那么几点,待人真诚,相互协助。做事大胆实践,认真对待,分而治之不断突破。但是关键在于你如何将这些点做到并且发挥到淋漓尽致。

最后awk的强大不仅限于本文,本文只是冰山一角,正如the awk programming所述需要在实践中出真知。希望自己在后续工作中可以使用awk提高工作效率。

References 

[1] the awk programming language—非常推荐chapter2(awk基本语法),chapter8(awk优缺点和性能分析)

[2] 左耳朵耗子的博客:awk简明教程http://coolshell.cn/articles/9070.html 

[3] awk实现某些sql命令 http://my.oschina.net/leejun2005/blog/100710

 

附录:

DIY1:假设好友列表是一组由英文逗号分隔的uids,并且好友列表的第一个uid是最亲密好友。如果我要打印所有uid和相应的亲密好友。如何实现?


DIY2:假设我们知道每个小文件容忍的最大行数为m,不知道文件总行数。这个时候,我们想将大文件的第一个m行数据拆分到第一个文件,第二个m行数据拆分到第二个文件,第i个m行数据拆分到第i个文件,直至大文件的所有行拆分完毕。使用awk如何进行实现?


DIY3:对user_info文件按照手机号那一列进行去重,即对具有相同手机号的两行,假设我们只保留手机号码第一次出现的那一行数据,代码应该如何修改呢?


DIY4:如何实现两个文件做交集和并集,比如求两个人的friends列表的交集,即他们的公共好友。比如求两个人的friends圈,即求两个人friends列表的并集。

 


 DIY5: 在user_info表中,为了更好统计数据,用1来表示male,用0来表示female。所以采用gsub来实现上述功能。


DIY6:假设有一tmp文件存储了9行以上数据,使用awk的关联数组打印出九九乘法表


DIY7:如果对排序算法比较熟悉,为了熟悉对awk程序控制语法。可以尝试使用awk实现插入排序,快速排序和堆排序。文件中存储了一列数据。在awk中还有asort和asorti两个内置的数组函数,you can have a try。


如果你觉得上述还不过瘾,可以尝试使用awk对排序算法进行跟踪测试和性能分析,具体参见the awk programming language

posted @ 2015-05-22 19:03  TsingTsing  阅读(498)  评论(1编辑  收藏  举报