分类: LINUX
2010-03-19 17:29:02
awk 是一种非常好的语言,适合于文本处理和报表生成,它还有许多精心设计的特性,允许进行需要特殊技巧程序设计。本文以及后续的文章将由浅入深通过实例讲解awk的用法。
首先,在当前目录里有个文件,假设文件名为address.in,内容如下:
1:abc:male:13612345678
2:mary:female:12345678
3:aaa:female:44445555
4:zlx:female:13550000000
5:lucy:female:13611111111
6:david:male:10101010
在命令行中输入以下命令:
$ awk '{ print }' address.in
结果将会显示address.in文件内容。调用awk时,我们指定address.in作为输入文件。执行awk时,它依次对address.in中的每一行执行print也就是打印命令。在 awk 中,花括号用于将几块代码组合到一起,这一点类似于 C 语言。在代码块中只有一条 print 命令。在 awk 中,如果只出现 print 命令,那么将打印当前行的全部内容。
若输入以下命令,它的作用与上例完全相同:
$ awk '{ print $0 }' address.in
在 awk 中,$0 变量表示整个当前行,所以 print 和 print $0 的作用完全一样。当然还可以创建一个 awk 程序,让它输出与输入数据完全无关的数据。以下是一个示例:
$ awk '{ print "" }' address.in
只要将 "" 字符串传递给 print 命令,它就会打印空白行。如果测试该脚本,将会发现对于 address.in 文件中的每一行,awk 都输出一个空白行。以下是另一个示例:
$ awk '{ print "haha" }' address.in
运行这个脚本将在您的屏幕上写6行haha。
接下来我们来看看多个字段。awk 非常善于处理分成多个逻辑字段的文本,而且让您可以毫不费力地引用 awk 脚本中每个独立的字段。以下脚本将打印出address.in文件中的联系人姓名列表:
$ awk -F":" '{ print $2 }' address.in
结果如下:
abc
mary
aaa
zlx
lucy
david
上例中,在调用 awk 时,使用 -F 选项来指定 ":" 作为字段分隔符。awk 处理 print $2 命令时,它会打印出在输入文件中每一行中出现的第二个字段。以下是另一个示例:
$ awk -F":" '{ print $2 $4 }' address.in
以下是该脚本的输入的输出结果:
abc13612345678
mary12345678
aaa44445555
zlx13550000000
lucy13611111111
david10101010
awk 打印出 address.in 文件的第二和第四个字段,它们正好分别是联系人姓名和电话。美中不足的是awk 会连接它们但不在它们之间添加空格。以下命令会在这两个字段中插入空格:
$ awk -F":" '{ print $2 " " $4 }' address.in
结果如下:
abc 13612345678
mary 12345678
aaa 44445555
zlx 13550000000
lucy 13611111111
david 10101010
以这种方式调用 print 时,它将连接 $2、" " 和 $4,创建可读的输出。当然,如果需要的话,我们还可以插入一些文本标签:
$ awk -F":" '{ print "name: " $2 "\ttel:" $4}' address.in
这将产生以下输出:
name: abc tel:13612345678
name: mary tel:12345678
name: aaa tel:44445555
name: zlx tel:13550000000
name: lucy tel:13611111111
name: david tel:10101010
非常简单吧,好了,今天就到这儿吧。后面跟进一步的用法以后再做讲解。前面一篇文章《awk简介》给大家简单介绍了awk,这篇文章开始,将陆续通过具体例子讲解awk的用法。为了讲解方便,将前篇文章中的address.in文件稍作修改,内容如下:
1:abc:male:13612345678
2:mary:female:12345678
3:aaa:female:44445555
4:zlx:female:13550000000
5:lucy:female:13611111111
6:david:male:10101010
7:anotherzlx:male:12125656
8:aaazlxbcd:male:12125656
9:zlxzlx:male:00000000
将脚本作为命令行自变量传递给 awk 对于小的单行程序来说是非常简单的,而对于多行程序,它就比较复杂。这样我们就需要在外部文件中撰写脚本。然后可以向 awk 传递 -f 选项,以向它提供此脚本文件。例如,我们在test.awk中键入内容如下:
BEGIN {
FS=":"
}
{print $1 }
然后在命令行键入如下命令:
awk -f test.awk address.in
则执行结果如下:
1
2
3
4
5
6
7
8
9
可见这个多行脚本与原来的单行脚本的作用相同,它们都打印出 address.in 中每一行的第一个字段。这两个方法的差别在于如何设置字段分隔符。在这个脚本中,字段分隔符在代码自身中指定(通过设置 FS 变量),而在前一个示例中,通过在命令行上向 awk 传递 -F":" 选项来设置 FS。通常,最好在脚本自身中设置字段分隔符,这样可以少输入一个命令行自变量。
那么代码中的BEGIN是什么意思呢?通常,对于每个输入行,awk 都会执行每个脚本代码块一次。然而,在许多编程情况中,可能需要在 awk 开始处理输入文件中的文本之前执行初始化代码。对于这种情况,awk 允许您定义一个 BEGIN 块。我们在前一个示例中使用了 BEGIN 块。因为 awk 在开始处理输入文件之前会执行 BEGIN 块,因此它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。awk 还提供了另一个特殊块,叫作 END 块。awk 在处理了输入文件中的所有行之后执行这个块。通常,END 块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
下面开始讲解规则表达式和块。awk 允许使用规则表达式,根据规则表达式是否匹配当前行来选择执行独立代码块。以下示例脚本只输出包含字符序列 zlx的那些行:
/zlx/ {print }
结果如下:
4:zlx:female:13550000000
7:anotherzlx:male:12125656
8:aaazlxbcd:male:12125656
9:zlxzlx:male:00000000
当然,可以使用更复杂的规则表达式。以下脚本将只打印包含浮点数的行:
/[0-9]+\.[0-9]*/ { print }
接下来我们来看表达式和条件语句。awk中有许多其它方法可以选择执行代码块。我们可以将任意一种布尔表达式放在一个代码块之前,以控制何时执行某特定块。仅当对前面的布尔表达式求值为真时,awk 才执行代码块。以下示例脚本输出将输出其第二个字段等于 zlx 的所有行中的第四个字段。如果当前行的第一个字段不等于 zlx,awk 将继续处理文件而不对当前行执行 print 语句:
$2 == "zlx" { print $4 }
结果如下:
13550000000
除了"=="之外,awk还提供了完整的比较运算符集合,包括 "<"、">"、"<="、">=" 和 "!="。另外,awk 还提供了 "~" 和 "!~" 运算符,它们分别表示“匹配”和“不匹配”。它们的用法是在运算符左边指定变量,在右边指定规则表达式。如果某一行的第二个字段包含字符序列 zlx,那么以下示例将只打印这一行中的第四个字段:
$2 ~ /zlx/ { print $4 }
结果如下:
13550000000
12125656
12125656
00000000
这个脚本也可以用条件语句实现如下:
{
if ( $2 ~ /zlx/ ) {
print $4
}
}
这两个脚本的功能完全一样。第一个示例中,布尔表达式放在代码块外面。而在第二个示例中,将对每一个输入行执行代码块,而且我们使用 if 语句来选择执行 print 命令。这两个方法都可以使用。
awk 还允许使用布尔运算符 "||"(逻辑与)和 "&&"(逻辑或),以便创建更复杂的布尔表达式:
( $2 !~ /zlx/ ) && ( $3== "male" ) { print }
这个示例只打印第二个字段不包括zlx且第三个字段等于male的那些行,结果为:
1:abc:male:13612345678
6:david:male:10101010
好了,今天就讲到这儿,下篇文章开始讲解awk中的变量。前面一篇文章《awk示例(一)》最后提到本文开始讲解awk中的变量,下面首先来看数值变量。
awk允许我们执行整数和浮点运算。通过使用数学表达式,可以很方便地编写计算文件中空白行数量的脚本。以下就是这样一个脚本(test.awk):
BEGIN { x=0 }
/^$/ { x=x+1 }
END { print "I found " x " blank lines. :)" }
aaa ffsdf
假设test.in内容如下:
fffh gfg hgfhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhghgf gdfgdfg ggggggggggggggggggg hhhhhhhhhhh
ggggggggggg
则执行 awk -f test.awk test.in后,结果如下:
I found 4 blank lines. :)
在BEGIN块中,将整数变量x初始化成零。然后,awk每次遇到空白行时,awk 将执行x=x+1语句,递增x。处理完所有行之后,执行END块,awk将打印出最终摘要,指出它找到的空白行数量。
下面继续看字符串变量,只要变量包含有效数字字符串,awk 会自动处理字符串到数字的转换步骤。例如:
x="1.01"
x=x+1
{print x}
执行上面示例之后awk将输出2.01,可见虽然将字符串值 1.01 赋值给变量 x,我们仍然可以对它加一。同样,假设名为address.in的文件内容如下:
1:abc:male:13612345678
2:mary:female:12345678
3:aaa:female:44445555
4:zlx:female:13550000000
5:lucy:female:13611111111
6:david:male:10101010
则执行awk -F":" '{ print ($1^2)+1 }' address.in后将对每个输入行的第一个字段乘方并加一,结果如下:
2
5
10
17
26
37
若将address.in的最后一行改为end:david:male:10101010,则同样执行上述命令后结果将会变为:
2
5
10
17
26
1
注意上述结果的最后一行是1,从这个例子可以看出如果某个特定变量不包含有效数字,awk 在对数学表达式求值时会将该变量当作数字零处理。
对于运算符,除了标准的加、减、乘、除,awk 还允许使用前面演示过的指数运算符 "^"、模(余数)运算符 "%" 和其它许多从 C 语言中借入的易于使用的赋值操作符,在此不做详细介绍。好了,上面我们简单介绍了awk中用户自定义变量的使用,下篇文章开始讲解awk自己的特殊变量。
前面一篇文章《awk示例(二)》主要讲解了awk中用户定义的变量,本文主要讲解awk自己的特殊变量。
首先开看字段分隔符FS,这个变量让您可以设置awk要查找的字段之间的字符序列。前面文章中我们使用address.in作为输入时,就是将 FS 设置成 ":"。其实FS 值并没有被限制为单一字符;可以通过指定任意长度的字符模式,将它设置成规则表达式。如果我们的address.in是由一个或多个 tab 分隔的字段,而不是“:”,则可以按以下方式设置 FS:
FS="\t+"
以上示例中,\t表示tab分割,我们使用特殊 "+" 规则表达式字符,它表示“一个或多个前一字符”,也就是一个或多个 tab。如果字段由空格分隔(一个或多个空格或 tab),则不需要设置FS,因为缺省FS就代表空格分隔。FS还可以是复杂的规则表达式。若我们的address.ini文件由"sep" 分隔,后面跟着三个数字,则以下规则表达式仍允许对数据进行正确的分析:
FS="sep[0-9][0-9][0-9]"
下面我们来看看“字段数量”NF变量,awk 会自动将该变量设置成当前记录中的字段数量。我们可以在条件语句中使用 NF 变量,如下面示例将判断输入文件中字段数量是否超过2个,若超过两个则显示每行的前三个字段:
{
if ( NF > 2 ) {
print $1 " " $2 " " $3
}
}
假设名为address.in的文件内容如下:
1:abc:male:13612345678
2:mary:female:12345678
3:aaa:female:44445555
4:zlx:female:13550000000
5:lucy:female:13611111111
6:david:male:10101010
若test.awk内容为:
BEGIN {
FS=":"
}
{
if ( NF > 2 ) {
print $1 " " $2 " " $3
}
}
则执行awk -f test.awk address.in后,结果为:
1 abc male
2 mary female
3 aaa female
4 zlx female
5 lucy female
6 david mal
但若address.in的文件内容为:
1:abc
2:mary
3:aaa
4:zlx
5:lucy
6:david
则执行awk -f test.awk address.in后屏幕上将没有任何输出。
最后,我们来看看记录号 (NR)变量,它始终包含当前记录的编号(awk 将第一个记录算作记录号 1)。还以本文开头部分的address.in文件为例,若test.awk内容为:
BEGIN {
FS=":"
}
{
if ( NR > 3) {
print $0
}
}
则执行awk -f test.awk address.in后,结果为:
4:zlx:female:13550000000
5:lucy:female:13611111111
6:david:male:10101010
也就是说该示例只打印记录号大于3的后三条记录。好了,本文就讲到这儿,通过前面的几篇文章,大家可以发现,awk非常擅长于文本处理,以后继续通过具体例子进一步讲解awk的特性。