AWK编程实例.docx
《AWK编程实例.docx》由会员分享,可在线阅读,更多相关《AWK编程实例.docx(30页珍藏版)》请在冰豆网上搜索。
AWK编程实例
AWK实用工具带有其自己的自包含语言,它是Unix/Linux中也是任何环境中现有的功能最强大的数据处理引擎之一。
这种编程及数据操作语言(其名称得自于它的创始人AlfredAho、PeterWeinberger和BrianKernighan姓氏的首个字母)的最大功能取决于一个人所拥有的知识。
它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。
AWK是什么?
最简单地说,AWK是一种用于处理文本的编程语言工具。
AWK实用工具的语言在很多方面类似于shell编程语言,尽管AWK具有完全属于其本身的语法。
在最初创造AWK时,其目的是用于文本处理,并且这种语言的基础是,只要在输入数据中有模式匹配,就执行一系列指令。
该实用工具扫描文件中的每一行,查找与命令行中所给定内容相匹配的模式。
如果发现匹配内容,则进行下一个编程步骤。
如果找不到匹配内容,则继续处理下一行。
尽管操作可能会很复杂,但命令的语法始终是:
awk'{pattern+action}'{filenames}
其中pattern表示AWK在数据中查找的内容,而action是在找到匹配内容时所执行的一系列命令。
花括号({})不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。
了解字段
实用工具将每个输入行分为记录和字段。
记录是单行的输入,而每条记录包含若干字段。
默认的字段分隔符是空格或制表符,而记录的分隔符是换行。
虽然在默认情况下将制表符和空格都看作字段分隔符(多个空格仍然作为一个分隔符),但是可以将分隔符从空格改为任何其它字符。
为了进行演示,请查看以下保存为emp_names的员工列表文件:
46012DULANEYEVANMOBILEAL
46013DURHAMJEFFMOBILEAL
46015STEENBILLMOBILEAL
46017FELDMANEVANMOBILEAL
46018SWIMSTEVEUNKNOWNAL
46019BOGUEROBERTPHOENIXAZ
46021JUNEMICAHPHOENIXAZ
46022KANESHERYLUNKNOWNAR
46024WOODWILLIAMMUNCIEIN
46026FERGUSSARAHMUNCIEIN
46027BUCKSARAHMUNCIEIN
46029TUTTLEBOBMUNCIEIN
当AWK读取输入内容时,整条记录被分配给变量$0。
每个字段以字段分隔符分开,被分配给变量$1、$2、$3等等。
一行在本质上可以包含无数个字段,通过字段号来访问每个字段。
因此,命令
awk'{print$1,$2,$3,$4,$5}'names
将会产生的打印输出是
46012DULANEYEVANMOBILEAL
46013DURHAMJEFFMOBILEAL
46015STEENBILLMOBILEAL
46017FELDMANEVANMOBILEAL
46018SWIMSTEVEUNKNOWNAL
46019BOGUEROBERTPHOENIXAZ
46021JUNEMICAHPHOENIXAZ
46022KANESHERYLUNKNOWNAR
46024WOODWILLIAMMUNCIEIN
46026FERGUSSARAHMUNCIEIN
46027BUCKSARAHMUNCIEIN
46029TUTTLEBOBMUNCIEIN
值得注意的一项重要内容是,AWK解释由空格分隔的五个字段,但当它打印显示内容时,在每个字段间只有一个空格。
利用为每个字段指定了唯一号码的功能,您可以选择只打印特定的字段。
例如,只打印每条记录的姓名时,只需选择第二个和第三个字段进行打印:
$awk'{print$2,$3}'emp_names
DULANEYEVAN
DURHAMJEFF
STEENBILL
FELDMANEVAN
SWIMSTEVE
BOGUEROBERT
JUNEMICAH
KANESHERYL
WOODWILLIAM
FERGUSSARAH
BUCKSARAH
TUTTLEBOB
$
您还可以指定按任何顺序打印字段,而无论它们在记录中是如何存在的。
因此,只需要显示姓名字段,并且使其顺序颠倒,先显示名字再显示姓氏:
$awk'{print$3,$2}'emp_names
EVANDULANEY
JEFFDURHAM
BILLSTEEN
EVANFELDMAN
STEVESWIM
ROBERTBOGUE
MICAHJUNE
SHERYLKANE
WILLIAMWOOD
SARAHFERGUS
SARAHBUCK
BOBTUTTLE
$
使用模式
通过包含一个必须匹配的模式,您可以选择只对特定的记录而不是所有的记录进行操作。
模式匹配的最简单形式是搜索,其中要匹配的项目被包含在斜线(/pattern/)中。
例如,只对那些居住在阿拉巴马州的员工执行前面的操作:
$awk'/AL/{print$3,$2}'emp_names
EVANDULANEY
JEFFDURHAM
BILLSTEEN
EVANFELDMAN
STEVESWIM
$
如果您不指定要打印的字段,则会打印整个匹配的条目:
$awk'/AL/'emp_names
46012DULANEYEVANMOBILEAL
46013DURHAMJEFFMOBILEAL
46015STEENBILLMOBILEAL
46017FELDMANEVANMOBILEAL
46018SWIMSTEVEUNKNOWNAL
$
对同一数据集的多个命令可以用分号(;)分隔开。
例如,在一行中打印姓名,而在另一行中打印城市和州名:
$awk'/AL/{print$3,$2;print$4,$5}'emp_names
EVANDULANEY
MOBILEAL
JEFFDURHAM
MOBILEAL
BILLSTEEN
MOBILEAL
EVANFELDMAN
MOBILEAL
STEVESWIM
UNKNOWNAL
$
如果没有使用分号(print$3,$2,$4,$5),则会在同一行中显示所有内容。
另一方面,如果分别给出两个打印语句,则会产生完全不同的结果:
$awk'/AL/{print$3,$2}{print$4,$5}'emp_names
EVANDULANEY
MOBILEAL
JEFFDURHAM
MOBILEAL
BILLSTEEN
MOBILEAL
EVANFELDMAN
MOBILEAL
STEVESWIM
UNKNOWNAL
PHOENIXAZ
PHOENIXAZ
UNKNOWNAR
MUNCIEIN
MUNCIEIN
MUNCIEIN
MUNCIEIN
$
只有在列表中找到AL时才会给出字段三和字段二。
但是,字段四和字段五是无条件的,始终打印它们。
只有第一组花括号中的命令对前面紧邻的命令(/AL/)起作用。
结果非常不便于阅读,可以使其稍微更清晰一些。
首先,在城市与州之间插入一个空格和逗号。
然后,在每两行显示之后放置一个空行:
$awk'/AL/{print$3,$2;print$4","$5"\n"}'emp_names
EVANDULANEY
MOBILE,AL
JEFFDURHAM
MOBILE,AL
BILLSTEEN
MOBILE,AL
EVANFELDMAN
MOBILE,AL
STEVESWIM
UNKNOWN,AL
$
在第四和第五个字段之间,添加一个逗号和一个空格(在引号之间),在第五个字段后面,打印一个换行符(\n)。
在AWK打印语句中还可以使用那些可在echo命令中使用的所有特殊字符,包括:
\n(换行)
\t(制表)
\b(退格)
\f(进纸)
\r(回车)
因此,要读取全部五个最初由制表符分隔开的字段,并且也利用制表符打印它们,您可以编程如下
$awk'{print$1"\t"$2"\t"$3"\t"$4"\t"$5}'emp_names
46012DULANEYEVANMOBILEAL
46013DURHAMJEFFMOBILEAL
46015STEENBILLMOBILEAL
46017FELDMANEVANMOBILEAL
46018SWIMSTEVEUNKNOWNAL
46019BOGUEROBERTPHOENIXAZ
46021JUNEMICAHPHOENIXAZ
46022KANESHERYLUNKNOWNAR
46024WOODWILLIAMMUNCIEIN
46026FERGUSSARAHMUNCIEIN
46027BUCKSARAHMUNCIEIN
46029TUTTLEBOBMUNCIEIN
$
通过连续设置多项标准并用管道(|)符号将其分隔开,您可以一次搜索多个模式匹配:
$awk'/AL|IN/'emp_names
46012DULANEYEVANMOBILEAL
46013DURHAMJEFFMOBILEAL
46015STEENBILLMOBILEAL
46017FELDMANEVANMOBILEAL
46018SWIMSTEVEUNKNOWNAL
46024WOODWILLIAMMUNCIEIN
46026FERGUSSARAHMUNCIEIN
46027BUCKSARAHMUNCIEIN
46029TUTTLEBOBMUNCIEIN
$
这样可找到每个阿拉巴马州和印第安那州居民的匹配记录。
但是在试图找出居住在亚利桑那州的人时,出现了一个问题:
$awk'/AR/'emp_names
46019BOGUEROBERTPHOENIXAZ
46021JUNEMICAHPHOENIXAZ
46022KANESHERYLUNKNOWNAZ
46026FERGUSSARAHMUNCIEIN
46027BUCKSARAHMUNCIEIN
$
员工46026和46027没有住在亚利桑那州;但是他们的名字中包含所搜索的字符序列。
切记,当在AWK中进行模式匹配时,例如grep、sed或者大部分其他Linux/Unix命令,将在记录(行)中的任何位置查找匹配,除非指定进行其他操作。
为解决这一问题,必须将搜索与特定字段联系起来。
通过利用代字号(?
)以及对特定字段的说明,可以达到这一目的,如下例所示:
$awk'$5?
/AR/'emp_names
46019BOGUEROBERTPHOENIXAZ
46021JUNEMICAHPHOENIXAZ
46022KANESHERYLUNKNOWNAZ
$
代字号(表示匹配)的对应符号是一个前面带有感叹号的代字号(!
?
)。
这些字符通知程序,如果搜索序列没有出现在指定字段中,则找出与搜索序列相匹配的所有行:
$awk'$5!
?
/AR/'names
46012DULANEYEVANMOBILEAL
46013DURHAMJEFFMOBILEAL
46015STEENBILLMOBILEAL
46017FELDMANEVANMOBILEAL
46018SWIMSTEVEUNKNOWNAL
46024WOODWILLIAMMUNCIEIN
46026FERGUSSARAHMUNCIEIN
46027BUCKSARAHMUNCIEIN
46029TUTTLEBOBMUNCIEIN
$
在这种情况下,将显示第五个字段中没有AR的所有行—包括两个Sarah条目,这两个条目确实包含AR,但却是在第三个字段而不是第五个字段中。
花括号和字段分隔符
括号字符在AWK命令中起着很重要的作用。
出现在括号之间的操作指出将要发生什么以及何时发生。
当只使用一对括号时:
括号间的所有操作同时发生。
{print$3,$2}
当使用多于一对的括号时:
执行第一组命令,在该命令完成后执行第二组命令。
{print$3}{print$2}
注意以下两列清单的区别:
$awk'{print$3,$2}'names
EVANDULANEY
JEFFDURHAM
BILLSTEEN
EVANFELDMAN
STEVESWIM
ROBERTBOGUE
MICAHJUNE
SHERYLKANE
WILLIAMWOOD
SARAHFERGUS
SARAHBUCK
BOBTUTTLE
$
$awk'{print$3}{print$2}'names
EVAN
DULANEY
JEFF
DURHAM
BILL
STEEN
EVAN
FELDMAN
STEVE
SWIM
ROBERT
BOGUE
MICAH
JUNE
SHERYL
KANE
WILLIAM
WOOD
SARAH
FERGUS
SARAH
BUCK
BOB
TUTTLE
$
要利用多组括号进行重复查找,执行第一组中的命令直到完成为止;然后处理第二组命令。
如果有第三组命令,则在第二组命令完成后执行它,以此类推。
在所生成的打印输出中,有两个分隔的打印命令,因此先执行第一个命令,随后执行第二个命令,这样导致每个条目显示在两行而不是一行中。
区分两个字段的字段分隔符不一定始终是空格;它可以是任何可识别的字符。
为进行演示,假定emp_names文件利用冒号而不是制表符来分隔字段:
$catemp_names
46012:
DULANEY:
EVAN:
MOBILE:
AL
46013:
DURHAM:
JEFF:
MOBILE:
AL
46015:
STEEN:
BILL:
MOBILE:
AL
46017:
FELDMAN:
EVAN:
MOBILE:
AL
46018:
SWIM:
STEVE:
UNKNOWN:
AL
46019:
BOGUE:
ROBERT:
PHOENIX:
AZ
46021:
JUNE:
MICAH:
PHOENIX:
AZ
46022:
KANE:
SHERYL:
UNKNOWN:
AR
46024:
WOOD:
WILLIAM:
MUNCIE:
IN
46026:
FERGUS:
SARAH:
MUNCIE:
IN
46027:
BUCK:
SARAH:
MUNCIE:
IN
46029:
TUTTLE:
BOB:
MUNCIE:
IN
$
如果试图通过指定所需要的第二个字段来打印姓氏
$awk'{print$2}'emp_names
您最后会得到十二个空行。
因为文件中没有空格,除了第一个字段之外没有可认别的字段。
为解决这一问题,必须通知AWK是空格之外的另一个字符作为分隔符,有两种方法可通知AWK使用新的字段分隔符:
使用命令行参数-F,或在程序中指定变量FS。
两种方法的效果相同,只有一种例外情况,如下例所示:
$awk'{FS=":
"}{print$2}'emp_names
DURHAM
STEEN
FELDMAN
SWIM
BOGUE
JUNE
KANE
WOOD
FERGUS
BUCK
TUTTLE
$
$awk-F:
'{print$2}'emp_names
DULANEY
DURHAM
STEEN
FELDMAN
SWIM
BOGUE
JUNE
KANE
WOOD
FERGUS
BUCK
TUTTLE
$
在第一个命令中,头一条记录返回不正确的空行,而其他结果正确。
直到读取第二条记录时,才识别字段分隔符并正确地执行。
通过使用BEGIN语句可以纠正这一缺点(在后文详述)。
-F的功能非常类似于BEGIN,能够正确地读取第一条记录并按要求执行。
在本文开始处我曾提到,默认的显示/输出字段分隔符是空格。
通过使用输出字段分隔符(OFS)变量,可以在程序中更改此特性。
例如,要读取文件(由冒号分隔)并以短划线显示,则命令是
$awk-F":
"'{OFS="-"}{print$1,$2,$3,$4,$5}'emp_names
46012-DULANEY-EVAN-MOBILE-AL
46013-DURHAM-JEFF-MOBILE-AL
46015-STEEN-BILL-MOBILE-AL
46017-FELDMAN-EVAN-MOBILE-AL
46018-SWIM-STEVE-UNKNOWN-AL
46019-BOGUE-ROBERT-PHOENIX-AZ
46021-JUNE-MICAH-PHOENIX-AZ
46022-KANE-SHERYL-UNKNOWN-AR
46024-WOOD-WILLIAM-MUNCIE-IN
46026-FERGUS-SARAH-MUNCIE-IN
46027-BUCK-SARAH-MUNCIE-IN
46029-TUTTLE-BOB-MUNCIE-IN
$
FS和OFS是(输入)字段分隔符和输出字段分隔符,它们只是一对可以在AWK实用工具中使用的变量。
例如,要在打印时为每行编号,可以采用以下方式使用NR变量:
$awk-F":
"'{printNR,$1,$2,$3}'emp_names
146012DULANEYEVAN
246013DURHAMJEFF
346015STEENBILL
446017FELDMANEVAN
546018SWIMSTEVE
646019BOGUEROBERT
746021JUNEMICAH
846022KANESHERYL
946024WOODWILLIAM
1046026FERGUSSARAH
1146027BUCKSARAH
1246029TUTTLEBOB
$
找出员工号码处于46012和46015之间的所有行:
$awk-F":
"'/4601[2-5]/'emp_names
46012DULANEYEVANMOBILEAL
46013DURHAMJEFFMOBILEAL
46015STEENBILLMOBILEAL
$
awk'BEGIN{sum=1};{print$1"\n"};{sum=sum+$1};END{printsum}'file
awk-vsum=1'BEGIN{sum=1};{print$1"\n"};{sum=sum+$1};END{printsum}'file
Built-inVariables
Gawk’sbuilt-invariablesare:
ARGCThenumberofcommandlinearguments(doesnotincludeoptionstogawk,ortheprogramsource).
ARGINDTheindexinARGVofthecurrentfilebeingprocessed.
ARGVArrayofcommandlinearguments.Thearrayisindexedfrom0toARGC-1.DynamicallychangingthecontentsofARGVcancontrolthe
filesusedfordata.
BINMODEOnnon-POSIXsystems,specifiesuseof“binary�
modeforallfileI/O.Numericvaluesof1,2,or3,specifythatinputfiles,output
files,orallfiles,respectively,shouldusebinaryI/O.Stringvaluesof"r",or"w"specifythatinputfiles,oroutputfiles,
respectively,shouldusebinaryI/O.Stringvaluesof"rw"or"wr"specifythatallfilesshouldusebinaryI/O.Anyotherstring
valueistreatedas"rw",butgeneratesawarningmessage.
CONVFMTTheconversionformatfornumbers,"%.6g",bydefault.
ENVIRONAnarraycontainingthevaluesofthecurrentenvironment.Thearrayisindexedbytheenvironmentvariables,eachelementbeingthe
valueofthatvariable(e.g.,ENVIRON["HOME"]mightbe/home/arnold).Changingthisarraydoesnotaffecttheenvironmentseenby
programswhichgawkspawnsviaredirectionorthesystem()function.
ERRNOIfasystemerroroccurseitherdoingaredirectionforgetline,duringa