AWK学习笔记

13 December 2010

by BoBo


一点历史
    AWK上世纪70年代诞生于传奇的Bell实验室,名字取于它的三位发明人,Alfred Aho, Peter Weinberger和 Brian Kernighan名字的首字母。发音类似《The AWK Programing Language》封面上的海雀(一种海鸟)的英文发音(auck[ɔ:k])。AWK最早出现在Unix V7中,Linux诞生后,AWK被LSB纳入其中,这意味者所有的Linux发行版都会提供awk的实现。目前linux上广发使用的是GNU实现的gawk,此外还有nawk,mawk,pgawk等版本。这些实现在AWK的语言规范基础上,扩展了一些其他功能,本文只关心最基础最通用的AWK规范,不探究相关实现的具体实现区别,欲知详情,请查看相关实现的手册。
以下是其中一位创始人Alfred V.Aho对AWK的描述。
    AWK is a language for processing files of text. A file is treated as a sequence of records, and by default each line is a record. Each line is broken up into a sequence of fields, so we can think of the first word in a line as the first field, the second word as the second field, and so on. An AWK program is of a sequence of pattern-action statements. AWK reads the input a line at a time. A line is scanned for each pattern in the program, and for each pattern that matches, the associated action is executed." - Alfred V.Aho
尊重原文,不翻译。

瞟一眼
准备一份数据文件datafile

Nike green X 10 10.00
Nike blue XL 10 10.00
Nike red L 10 12.00
Adiddas yellow L 10 10.00
Adiddas brown M 10 10.00
Adiddas gray M 20 8.00

打开终端,执行

awk '/Nike/ {print $2}' datafile

输出

green
blue
red

解释下:
awk表示AWK解析器命令
两个'之间的部分表示这次执行的命令行脚本,其中/Nike/叫模式pattern,{print $2}叫操作action,print表示打印,$2表示每一记录中的第2个域。
datafile表示输入文件。
整个命令的意思表示:
对datafile中所有匹配Nike的行,执行print $2操作,打印含有Nike行中的第二个被空白符隔开的字段。
晕了么?来看点概念的解释吧:)

一些枯燥的概念
输入文件:待处理的数据文件datafile,每个输入文件为多个记录的集合
记录分割符:将输入文件划分为多个记录的符号,默认为'/n'
记录:默认数据文件中的每一行,如"Nike    green   X   10  10.00",每个记录是多个域的集合
域分隔符:将每个记录划分为多个域的符号,默认为空白符
域:记录被域分割符划分后的单元,每个输入文件中的最小处理单元。如Nike, green,X,10,10.00分别为第一个记录的第1,2,3,4,5个域(注意,这里不是从0开始计数的)
Buffer:AWK中支持两种Buffer,域buffer和记录buffer
record buffer:表示整条记录,可以通过$0获取
field buffer:当前记录中的每一个field,可以通过$1,$2,$3获取。
内置变量:
FS:Field separator,域分隔符,默认为空白符
RS:Record Separator,记录分隔符,默认为换行符
NF:Number of Fields in current record,当前记录中的域数
NR:Number of the current Record,当前的记录数
OFS:Output Field Separator,输出域分隔符,默认为空白符
ORS:Output Record Separator,输出记录分隔符,默认为换行符
FILENAME: Current filename,当前输入文件名

针对一个awk命令行

awk 'pattern {action}' datafile
awk的执行流程:
1.读取:awk会按行读取输入文件(Line by Line)
2.切分:将输入行划分为多个域
3.匹配:根据pattern匹配
4.执行:对匹配的行执行action

命令行
AWK的命令行语法有两种形式


awk [options] -f program-file file...
awk [options] 'program-text' file...


前者通过-f参数指定使用后面的名为program-file的文件作为AWK脚本处理输入文件file
后者表示 program-text是一段AWK命令行脚本,用于处理输入文件file.
options表示参数可选项,具体参数列表请参考相关手册。
基本语法:
当通过awk [options] 'program-text' file...方式执行awk时,program-text表示一段脚本。AWK的命令行脚本由模式和操作两部分组成:
pattern {action},表示对输入文件中所有匹配pattern的行,执行action操作。
缺少pattern时,默认对输入文件中所有行执行action
缺少action时,默认向标准输出打印所有匹配pattern的行
两者都没有时,不进行任何操作。


例子:
awk '/for/ {print $1}'    datafile 表示打印datafile中所有匹配含有for的行的第一个域(关于域,请看下文)
awk '/for/'    datafile 表示打印datafile中所有包含for的行
awk '{print}'    datafile 表示打印datafile中所有行

模式pattern:

pattern分为Simple pattern和Range pattern两大类。
Simple Pattern包括:
a.BEGIN:标示对输入文件进行处理之前
b.END:标示对输入文件完成处理之后
c.EXPRESSION:表达式,包括正则表达式,算数表达式,逻辑表达式
d.NOTHING:什么都没有,表示无pattern,默认匹配所有行
Expression Pattern:
正则表达式/regx/:
通过两个/标记的pattern为正则表达式,如/^bi/匹配所有以bi开始的记录,正则表达式语法与grep语法类似,不再赘述,请查看相关手册。
需要注意的是 ~/regx/表示不匹配,如~/^bi/表示不是以bi开始的所有记录。
算数表达式:
模式$3*$4>100 表示匹配所有$3与$4的积大于100的记录,类似的算数操作符还包括:
逻辑表达式:
模式 $NR>2 && /^bi/ 表示匹配所有行数大于2以bi开头的的行, &&表示逻辑与

Range Pattern
Range Pattern用于匹配输入行中一个连续的区域,其语法由两个Simple Pattern构成:
pattern1, pattern2 {action}
pattern1表示从第一个匹配pattern1的行开始,执行action操作。
pattern2表示从第一个匹配pattern2的行开始,停止执行action操作。
例子:

awk '/blue/,/yellow/ {print}' datafile


表示从datafile中第一匹配blue的行开始执行print操作,打印整行,到第一个匹配yellow的行为止,停止打印。

awk '$NR==2, $NR==10 {print}' datafile

表示打印datafile中的第2~10行。

操作Action
Action语句分为:表达式,输出,判断,循环,和控制语句。
表达式
作为一门编程语言,AWK与其他语言一样,有变量,运算符,可以进行表达式计算。
变量
AWK是弱类型的,变量无须声明,且第一次引用后就一直存在。所有的变量都被初始化为空字符串。变量名必须以字母开头,可以包含字母,数字和下划线。AWK支持三类变量,数字,字符串,数组。
需要特殊说明的是数组,数组只支持一维,且索引可以是数字也可以是字符串(因此我更倾向于它是个map)。可以通过

delete arr[index]

删除数组arr中index元素

赋值操作符
= assign result of right-hand-side expression to left-hand-side variable
++ Add 1 to variable
-- Subtract 1 from variable
+= Assign result of addition
-= Assign result of subtraction
*= Assign result of multiplication
/= Assign result of division
%= Assign result of modulo
^= Assign result of exponentiation
输出语句
AWK中有三种用于输出的内置函数:
print 直接输出到终端
printf 格式化输出到终端(与C中的printf类似)
sprintf 格式化字符串(与C中不同的是,通过返回值方式返回新的字符串)
可以通过输出重定向将输出打印到其他地方:
> "file" 清空file,并打印输出到file
>> "file" 将输出追加到file末尾
| "command" 管道符,将输出作为command命令的输入。

条件判断
if-else语法:

if (条件语句)
执行语句1
else
执行语句2

例子:

if(NR<3)
print $2
else
print $3


循环
for循环
语法

for(初始条件;限制条件;条件更新)
执行语句

例子

for(i=1; i<NR; i++)
{
total +=$i;
count++
}


do-while循环
语法

do
执行语句
while(条件)

例子

i=1
do {
print $0
i++
} while (i<=10)


AWK同样支持break,continue两个循环控制语句。

脚本编程
上文主要讲述了命令行下,AWK可以进行操作的基本知识,事实上,AWK作为一种编程语言,可以独立写成脚本文件,采用

awk [options] -f script-file inputfile...

的方式执行。
通常一个awk包含三段式基本结构

--------BEGIN区----------
| BEGIN {初始化操作 } |
--------命令执行----------
| pattern {action}       |
| ...
| pattern {action}       |
---------END区-----------
| EDN  {结尾操作}       |
----------------------------
BEGIN语句进行文件处理前的初始化操作,如定义FS,定义变量值等。该语句在一次文件处理过程中只在处理前执行一次
中间的命令执行是对每一个输入记录都进行的操作,语法与上述的基础语法一样
END区只在文件处理完成后执行一次。
例子

BEGIN { FS=: }
/for/ {print $2}
END {print "this is end!" }


先定义域分隔符为":",然后对所有匹配for的记录,打印第2个域,文件处理完后打印"this is end!"

内置函数
AWK提供一些内置函数。
字符串内置函数:
sub (regex, substr), 使用substr替换匹配自左向右做大匹配regex的部分。
gsub (regex,substr), 与sub类似,只是在全文中进行替换
length(string), 获取string字符串的长度
toupper(string), 将string转换为大写
tolower(string), 将string转换为小写
split(string, array, field separator), 将string以field separator为分隔符分割为数组存储到array中。
以上只是常用的一些内置函数,其他请参考相关手册

示例
分析日志,现有一份日志pic.log,格式如下,需要提取出其中的url链接

INFO 10/18/2010 00:00:05--offerId=[836952303]. url=[http://f7.images22.51img1.com/6000/hw3255311/7205a450f1ea2751f31aa8c3ad1dd2be.jpg].
INFO 10/18/2010 00:00:05--offerId=[836952303]. url=[http://fb.images22.51img1.com/6000/hw3255311/bfe077567231f7d8efae4a1ac1b19477.jpg].
INFO 10/18/2010 00:00:05--offerId=[836952303]. url=[http://f6.images22.51img1.com/6000/hw3255311/6106d6366351d32c68e3f8a332b52da9.jpg].
INFO 10/18/2010 00:00:05--offerId=[836952303]. url=[http://f6.images22.51img1.com/6000/hw3255311/6106d6366351d32c68e3f8a332b52da9.jpg].

脚本

awk -F "[" '{split($3, url, "]");print url[1]}' pic.log

通过 -F ":"定义域分隔符为"[",这样每行数据会被划分为三个域

INFO 10/18/2010 00:00:05--offerId= --[-- 836952303]. url= --[-- http://f7.images22.51img1.com/6000/hw3255311/7205a450f1ea2751f31aa8c3ad1dd2be.jpg].

所需要提取的url位于第三个域中,通过内置函数

split($3, url, "]")

将第三个域以"]"分割,存储到url数组中,url数组为["http://f7.images22.51img1.com/6000/hw3255311/7205a450f1ea2751f31aa8c3ad1dd2be.jpg", "."]
url[1]即为所需的url

参考资料
a. man awk
b. AWK wiki,http://en.wikipedia.org/wiki/AWK
c.《AWK学习笔记》http://man.lupaworld.com/content/manage/ringkee/awk.htm



blog comments powered by Disqus