Awk 实例第 1部分.docx
《Awk 实例第 1部分.docx》由会员分享,可在线阅读,更多相关《Awk 实例第 1部分.docx(17页珍藏版)》请在冰点文库上搜索。
Awk实例第1部分
Awk是一种非常好的语言,同时有一个非常奇怪的名称。
在本系列(共三篇文章)的第一篇文章中,DanielRobbins将使您迅速掌握awk编程技巧。
随着本系列的进展,将讨论更高级的主题,最后将演示一个真正的高级awk演示程序。
捍卫awk
在本系列文章中,我将使您成为精通awk的编码人员。
我承认,awk并没有一个非常好听且又非常“时髦”的名字。
awk的GNU版本(叫作gawk)听起来非常怪异。
那些不熟悉这种语言的人可能听说过"awk",并可能认为它是一组落伍且过时的混乱代码。
它甚至会使最博学的UNIX权威陷于错乱的边缘(使他不断地发出"kill-9!
"命令,就象使用咖啡机一样)。
的确,awk没有一个动听的名字。
但它是一种很棒的语言。
awk适合于文本处理和报表生成,它还有许多精心设计的特性,允许进行需要特殊技巧程序设计。
与某些语言不同,awk的语法较为常见。
它借鉴了某些语言的一些精华部分,如C语言、python和bash(虽然在技术上,awk比python和bash早创建)。
awk是那种一旦学会了就会成为您战略编码库的主要部分的语言。
回页首
第一个awk
让我们继续,开始使用awk,以了解其工作原理。
在命令行中输入以下命令:
$awk'{print}'/etc/passwd
您将会见到/etc/passwd文件的内容出现在眼前。
现在,解释awk做了些什么。
调用awk时,我们指定/etc/passwd作为输入文件。
执行awk时,它依次对/etc/passwd中的每一行执行print命令。
所有输出都发送到stdout,所得到的结果与与执行catting/etc/passwd完全相同。
现在,解释{print}代码块。
在awk中,花括号用于将几块代码组合到一起,这一点类似于C语言。
在代码块中只有一条print命令。
在awk中,如果只出现print命令,那么将打印当前行的全部内容。
这里是另一个awk示例,它的作用与上例完全相同:
$awk'{print$0}'/etc/passwd
在awk中,$0变量表示整个当前行,所以print和print$0的作用完全一样。
如果您愿意,可以创建一个awk程序,让它输出与输入数据完全无关的数据。
以下是一个示例:
$awk'{print""}'/etc/passwd
只要将""字符串传递给print命令,它就会打印空白行。
如果测试该脚本,将会发现对于/etc/passwd文件中的每一行,awk都输出一个空白行。
再次说明,awk对输入文件中的每一行都执行这个脚本。
以下是另一个示例:
$awk'{print"hiya"}'/etc/passwd
运行这个脚本将在您的屏幕上写满hiya。
:
)
回页首
多个字段
awk非常善于处理分成多个逻辑字段的文本,而且让您可以毫不费力地引用awk脚本中每个独立的字段。
以下脚本将打印出您的系统上所有用户帐户的列表:
$awk-F":
"'{print$1}'/etc/passwd
上例中,在调用awk时,使用-F选项来指定":
"作为字段分隔符。
awk处理print$1命令时,它会打印出在输入文件中每一行中出现的第一个字段。
以下是另一个示例:
$awk-F":
"'{print$1$3}'/etc/passwd
以下是该脚本输出的摘录:
halt7
operator11
root0
shutdown6
sync5
bin1
....etc.
如您所见,awk打印出/etc/passwd文件的第一和第三个字段,它们正好分别是用户名和用户标识字段。
现在,当脚本运行时,它并不理想--在两个输出字段之间没有空格!
如果习惯于使用bash或python进行编程,那么您会指望print$1$3命令在两个字段之间插入空格。
然而,当两个字符串在awk程序中彼此相邻时,awk会连接它们但不在它们之间添加空格。
以下命令会在这两个字段中插入空格:
$awk-F":
"'{print$1""$3}'/etc/passwd
以这种方式调用print时,它将连接$1、""和$3,创建可读的输出。
当然,如果需要的话,我们还可以插入一些文本标签:
$awk-F":
"'{print"username:
"$1"\t\tuid:
"$3"}'/etc/passwd
这将产生以下输出:
username:
haltuid:
7
username:
operatoruid:
11
username:
rootuid:
0
username:
shutdownuid:
6
username:
syncuid:
5
username:
binuid:
1
....etc.
回页首
外部脚本
将脚本作为命令行自变量传递给awk对于小的单行程序来说是非常简单的,而对于多行程序,它就比较复杂。
您肯定想要在外部文件中撰写脚本。
然后可以向awk传递-f选项,以向它提供此脚本文件:
$awk-fmyscript.awkmyfile.in
将脚本放入文本文件还可以让您使用附加awk功能。
例如,这个多行脚本与前面的单行脚本的作用相同,它们都打印出/etc/passwd中每一行的第一个字段:
BEGIN{
FS=":
"
}
{print$1}
这两个方法的差别在于如何设置字段分隔符。
在这个脚本中,字段分隔符在代码自身中指定(通过设置FS变量),而在前一个示例中,通过在命令行上向awk传递-F":
"选项来设置FS。
通常,最好在脚本自身中设置字段分隔符,只是因为这表示您可以少输入一个命令行自变量。
我们将在本文的后面详细讨论FS变量。
回页首
BEGIN和END块
通常,对于每个输入行,awk都会执行每个脚本代码块一次。
然而,在许多编程情况中,可能需要在awk开始处理输入文件中的文本之前执行初始化代码。
对于这种情况,awk允许您定义一个BEGIN块。
我们在前一个示例中使用了BEGIN块。
因为awk在开始处理输入文件之前会执行BEGIN块,因此它是初始化FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。
awk还提供了另一个特殊块,叫作END块。
awk在处理了输入文件中的所有行之后执行这个块。
通常,END块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
回页首
规则表达式和块
awk允许使用规则表达式,根据规则表达式是否匹配当前行来选择执行独立代码块。
以下示例脚本只输出包含字符序列foo的那些行:
/foo/{print}
当然,可以使用更复杂的规则表达式。
以下脚本将只打印包含浮点数的行:
/[0-9]+\.[0-9]*/{print}
回页首
表达式和块
还有许多其它方法可以选择执行代码块。
我们可以将任意一种布尔表达式放在一个代码块之前,以控制何时执行某特定块。
仅当对前面的布尔表达式求值为真时,awk才执行代码块。
以下示例脚本输出将输出其第一个字段等于fred的所有行中的第三个字段。
如果当前行的第一个字段不等于fred,awk将继续处理文件而不对当前行执行print语句:
$1=="fred"{print$3}
awk提供了完整的比较运算符集合,包括"=="、"<"、">"、"<="、">="和"!
="。
另外,awk还提供了"~"和"!
~"运算符,它们分别表示“匹配”和“不匹配”。
它们的用法是在运算符左边指定变量,在右边指定规则表达式。
如果某一行的第五个字段包含字符序列root,那么以下示例将只打印这一行中的第三个字段:
$5~/root/{print$3}
回页首
条件语句
awk还提供了非常好的类似于C语言的if语句。
如果您愿意,可以使用if语句重写前一个脚本:
{
if($5~/root/){
print$3
}
}
这两个脚本的功能完全一样。
第一个示例中,布尔表达式放在代码块外面。
而在第二个示例中,将对每一个输入行执行代码块,而且我们使用if语句来选择执行print命令。
这两个方法都可以使用,可以选择最适合脚本其它部分的一种方法。
以下是更复杂的awkif语句示例。
可以看到,尽管使用了复杂、嵌套的条件语句,if语句看上去仍与相应的C语言if语句一样:
{
if($1=="foo"){
if($2=="foo"){
print"uno"
}else{
print"one"
}
}elseif($1=="bar"){
print"two"
}else{
print"three"
}
}
使用if语句还可以将代码:
!
/matchme/{print$1$3$4}
转换成:
{
if($0!
~/matchme/){
print$1$3$4
}
}
这两个脚本都只输出不包含matchme字符序列的那些行。
此外,还可以选择最适合您的代码的方法。
它们的功能完全相同。
awk还允许使用布尔运算符"||"(逻辑与)和"&&"(逻辑或),以便创建更复杂的布尔表达式:
($1=="foo")&&($2=="bar"){print}
这个示例只打印第一个字段等于foo且第二个字段等于bar的那些行。
回页首
数值变量!
至今,我们不是打印字符串、整行就是特定字段。
然而,awk还允许我们执行整数和浮点运算。
通过使用数学表达式,可以很方便地编写计算文件中空白行数量的脚本。
以下就是这样一个脚本:
BEGIN{x=0}
/^$/{x=x+1}
END{print"Ifound"x"blanklines.:
)"}
在BEGIN块中,将整数变量x初始化成零。
然后,awk每次遇到空白行时,awk将执行x=x+1语句,递增x。
处理完所有行之后,执行END块,awk将打印出最终摘要,指出它找到的空白行数量。
回页首
字符串化变量
awk的优点之一就是“简单和字符串化”。
我认为awk变量“字符串化”是因为所有awk变量在内部都是按字符串形式存储的。
同时,awk变量是“简单的”,因为可以对它执行数学操作,且只要变量包含有效数字字符串,awk会自动处理字符串到数字的转换步骤。
要理解我的观点,请研究以下这个示例:
x="1.01"
#Wejustsetxtocontainthe*string*"1.01"
x=x+1
#Wejustaddedonetoa*string*
printx
#Incidentally,thesearecomments:
)
awk将输出:
2.01
有趣吧!
虽然将字符串值1.01赋值给变量x,我们仍然可以对它加一。
但在bash和python中却不能这样做。
首先,bash不支持浮点运算。
而且,如果bash有“字符串化”变量,它们并不“简单”;要执行任何数学操作,bash要求我们将数字放到丑陋的$())结构中。
如果使用python,则必须在对1.01字符串执行任何数学运算之前,将它转换成浮点值。
虽然这并不困难,但它仍是附加的步骤。
如果使用awk,它是全自动的,而那会使我们的代码又好又整洁。
如果想要对每个输入行的第一个字段乘方并加一,可以使用以下脚本:
{print($1^2)+1}
如果做一个小实验,就可以发现如果某个特定变量不包含有效数字,awk在对数学表达式求值时会将该变量当作数字零处理。
回页首
众多运算符
awk的另一个优点是它有完整的数学运算符集合。
除了标准的加、减、乘、除,awk还允许使用前面演示过的指数运算符"^"、模(余数)运算符"%"和其它许多从C语言中借入的易于使用的赋值操作符。
这些运算符包括前后加减(i++、--foo)、加/减/乘/除赋值运算符(a+=3、b*=2、c/=2.2、d-=6.2)。
不仅如此--我们还有易于使用的模/指数赋值运算符(a^=2、b%=4)。
回页首
字段分隔符
awk有它自己的特殊变量集合。
其中一些允许调整awk的运行方式,而其它变量可以被读取以收集关于输入的有用信息。
我们已经接触过这些特殊变量中的一个,FS。
前面已经提到过,这个变量让您可以设置awk要查找的字段之间的字符序列。
我们使用/etc/passwd作为输入时,将FS设置成":
"。
当这样做有问题时,我们还可以更灵活地使用FS。
FS值并没有被限制为单一字符;可以通过指定任意长度的字符模式,将它设置成规则表达式。
如果正在处理由一个或多个tab分隔的字段,您可能希望按以下方式设置FS:
FS="\t+"
以上示例中,我们使用特殊"+"规则表达式字符,它表示“一个或多个前一字符”。
如果字段由空格分隔(一个或多个空格或tab),您可能想要将FS设置成以下规则表达式:
FS="[[:
space:
]+]"
这个赋值表达式也有问题,它并非必要。
为什么?
因为缺省情况下,FS设置成单一空格字符,awk将这解释成表示“一个或多个空格或tab”。
在这个特殊示例中,缺省FS设置恰恰是您最想要的!
复杂的规则表达式也不成问题。
即使您的记录由单词"foo"分隔,后面跟着三个数字,以下规则表达式仍允许对数据进行正确的分析:
FS="foo[0-9][0-9][0-9]"
回页首
字段数量
接着我们要讨论的两个变量通常并不是需要赋值的,而是用来读取以获取关于输入的有用信息。
第一个是NF变量,也叫做“字段数量”变量。
awk会自动将该变量设置成当前记录中的字段数量。
可以使用NF变量来只显示某些输入行:
NF==3{print"thisparticularrecordhasthreefields:
"$0}
当然,也可以在条件语句中使用NF变量,如下:
{
if(NF>2){
print$1""$2":
"$3
}
}
回页首
记录号
记录号(NR)是另一个方便的变量。
它始终包含当前记录的编号(awk将第一个记录算作记录号1)。
迄今为止,我们已经处理了每一行包含一个记录的输入文件。
对于这些情况,NR还会告诉您当前行号。
然而,当我们在本系列以后部分中开始处理多行记录时,就不会再有这种情况,所以要注意!
可以象使用NF变量一样使用NR来只打印某些输入行:
(NR<10)||(NR>100){print"Weareonrecordnumber1-9or101+"}
另一个示例:
{
#skipheader
if(NR>10){
print"ok,nowfortherealinformation!
"
}
}
awk提供了适合各种用途的附加变量。
我们将在以后的文章中讨论这些变量。
现在已经到了初次探索awk的尾声。
随着本系列的开展,我将演示更高级的awk功能,我们将用一个真实的awk应用程序作为本系列的结尾。
同时,如果急于学习更多知识,请参考以下列出的参考资料。
参考资料
∙您可以参阅本文在developerWorks全球站点上的英文原文.
∙如果想看好的老式书籍,O'Reilly的sed&awk,2ndEdition是极佳选择。
∙请参考comp.lang.awkFAQ。
它还包含许多附加awk链接。
∙PatrickHartigan的awktutorial还包括了实用的awk脚本。
∙Thompson'sTAWKCompiler将awk脚本编译成快速二进制可执行文件。
可用版本有Windows版、OS/2版、DOS版和UNIX版。
∙TheGNUAwkUser'sGuide可用于在线参考。
关于作者
DanielRobbins居住在新墨西哥州的Albuquerque。
他是GentooTechnologies,Inc.的总裁兼CEO,GentooLinux(用于PC的高级Linux)和Portage系统(Linux的下一代移植系统)的创始人。
他还是Macmillan书籍CalderaOpenLinuxUnleashed、SuSELinuxUnleashed和SambaUnleashed的合作者。
Daniel自二年级起就与计算机某些领域结下不解之缘,那时他首先接触的是Logo程序语言,并沉溺于Pac-Man游戏中。
这也许就是他至今仍担任SONYElectronicPublishing/Psygnosis的首席图形设计师的原因所在。
Daniel喜欢与妻子Mary和新出生的女儿Hadassah一起共度时光。
可通过mailto:
drobbins@gentoo.org?
cc=drobbins@gentoo.org与Daniel联系。