天天看点

awk

Awk简介  AWK是一种优良的文本处理工具。它不仅是 Linux 中也是任何环境中现有的功能最强大的数据处理引擎之一。这种编程及数据操作语言(其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母)的最大功能取决于一个人所拥有的知识。 AWK 提供了极其强大的功能:可以进行样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。它具备了一个完整的语言所应具有的几乎所有精美特性。实际上 AWK 的确拥有自己的语言: AWK 程序设计语言, 三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。

  最简单地说, AWK 是一种用于处理文本的编程语言工具。AWK 在很多方面类似于 shell 编程语言,尽管 AWK 具有完全属于其本身的语法。它的设计思想来源于 SNOBOL4 、sed 、Marc Rochkind设计的有效性语言、语言工具 yacc 和 lex ,当然还从 C 语言中获取了一些优秀的思想。在最初创造 AWK 时,其目的是用于文本处理,并且这种语言的基础是,只要在输入数据中有模式匹配,就执行一系列指令。该实用工具扫描文件中的每一行,查找与命令行中所给定内容相匹配的模式。如果发现匹配内容,则进行下一个编程步骤。如果找不到匹配内容,则继续处理下一行。

  尽管操作可能会很复杂,但命令的语法始终是:

  awk '{pattern + action}'

  其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号 ({}) 不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。

  gawk 是 AWK 的 GNU 版本。

  一般的UNIX作业系统,本身即附有AWK,不同的UNIX作业系统所附的AWK其版本亦不尽相同,若读者所使用的系统上未附有AWK,可通过 anonymous ftp 到下列地方取得:

  phi.sinica.edu.tw:/pub/gnu

  ftp.edu.tw:/UNIX/gnu

  prep.ai.mit.edu:/pub/gnu

  注 解:一种编程语言,因其模式匹配语法而特别有用,通常用于数据检索和数据转换。一个GNU版本称为Gawk。

  awk提供了适应多种需要的不同解决方案,它们是:

  一、 awk命令行,你可以象使用普通UNIX命令一样使用awk,在命令行中你也可以使用awk程序设计语言,虽然awk支持多行的录入,但是录入长长的命令行并保证其正确无误却是一件令人头疼的事,因此,这种方法一般只用于解决简单的问题。当然,你也可以在shell script程序中引用awk命令行甚至awk程序脚本。

  二、使用-f选项调用awk程序。awk允许将一段awk程序写入一个文本文件,然后在awk命令行中用-f选项调用并执行这段程序。具体的方法我们将在后面的awk语法中讲到。

  三、利用命令解释器调用awk程序:利用UNIX支持的命令解释器功能,我们可以将一段awk程序写入文本文件,然后在它的第一行加上:

  #!/bin/awk -f

  并赋予这个文本文件以执行的权限。这样做之后,你就可以在命令行中用类似于下面这样的方式调用并执行这段awk程序了。

  awk脚本文本名 待处理文件

  与其它UNIX命令一样,awk拥有自己的语法:

  awk [ -F re] [parameter...] ['prog'] [-f progfile]

  参数说明:

  -F re:允许awk更改其字段分隔符。

  parameter: 该参数帮助为不同的变量赋值。

  'prog': awk的程序语句段。这个语句段必须用单拓号:'和'括起,以防被shell解释。这个程序语句段的标准形式为:

  'pattern '

  其中pattern参数可以是egrep正则表达式中的任何一个,它可以使用语法/re/再加上一些样式匹配技巧构成。与sed类似,你也可以使用"," 分开两样式以选择某个范围。关于匹配的细节,你可以参考附录,如果仍不懂的话,找本UNIX书学学grep和sed(本人是在学习ed时掌握匹配技术的)。 action参数总是被大括号包围,它由一系列awk语句组成,各语句之间用";"分隔。awk解释它们,并在pattern给定的样式匹配的记录上执行其操作。与shell类似,你也可以使用“#”作为注释符,它使“#”到行尾的内容成为注释,在解释执行时,它们将被忽略。你可以省略pattern和 action之一,但不能两者同时省略,当省略pattern时没有样式匹配,表示对所有行(记录)均执行操作,省略action时执行缺省的操作——在标准输出上显示。

  -f progfile:允许awk调用并执行progfile指定有程序文件。progfile是一个文本文件,他必须符合awk的语法。

  in_file:awk的输入文件,awk允许对多个输入文件进行处理。值得注意的是awk不修改输入文件。如果未指定输入文件,awk将接受标准输入,并将结果显示在标准输出上。awk支持输入输出重定向。

  前面说过,awk处理的工作与数据库的处理方式有相同之处,其相同处之一就是awk支持对记录和字段的处理,其中对字段的处理是grep和sed不能实现的,这也是awk优于二者的原因之一。在awk中,缺省的情况下总是将文本文件中的一行视为一个记录,而将一行中的某一部分作为记录中的一个字段。为了操作这些不同的字段,awk借用shell的方法,用1,2,3...这样的方式来顺序地表示行(记录)中的不同字段。特殊地,awk用0表示整个行(记录)。不同的字段之间是用称作分隔符的字符分隔开的。系统默认的分隔符是空格。awk允许在命令行中用-F re的形式来改变这个分隔符。事实上,awk用一个内置的变量FS来记忆这个分隔符。awk中有好几个这样的内置变量,例如,记录分隔符变量RS、当前工作的记录数NR等等,本文后面的附表列出了全部的内置变量。这些内置的变量可以在awk程序中引用或修改,例如,你可以利用NR变量在模式匹配中指定工作范围,也可以通过修改记录分隔符RS让一个特殊字符而不是换行符作为记录的分隔符。

  例:显示文本文件myfile中第七行到第十五行中以字符%分隔的第一字段,第三字段和第七字段:

  awk -F % 'NR==7,NR==15 {print $1 $3 $7}' myfile

  awk 之所以成为一种优秀的程序设计语言的原因之一是它吸收了某些优秀的程序设计语言(例如C)语言的许多优点。这些优点之一就是内置函数的使用,awk定义并支持了一系列的内置函数,由于这些函数的使用,使得awk提供的功能更为完善和强大,例如,awk使用了一系列的字符串处理内置函数(这些函数看起来与C 语言的字符串处理函数相似,其使用方式与C语言中的函数也相差无几),正是由于这些内置函数的使用,使awk处理字符串的功能更加强大。本文后面的附录中列有一般的awk所提供的内置函数,这些内置函数也许与你的awk版本有些出入,因此,在使用之前,最好参考一下你的系统中的联机帮助。

  内置的字符串函数

gsub(r,s)

在整个$0中用s代替r

gsub(r,s,t)

在整个t中用s替代r

index(s,t)

返回s中字符串t的第一位置

length(s)

返回s长度

match(s,r)

测试s是否包含匹配r的字符串

split(s,a,fs)

在fs上将s分成序列a

sprint(fmt,exp)

返回经fmt格式化后的exp

sub(r,s)

用$0中最左边最长的子串代替s

substr(s,p)

返回字符串s中从p开始的后缀部分

substr(s,p,n)

返回字符串s中从p开始长度为n的后缀部分

 gsub函数有点类似于sed查找和替换。它允许替换一个字符串或字符为另一个字符串或字符,并以正则表达式的形式执行。第一个函数作用于记录$0,第二个gsub函数允许指定目标,然而,如果未指定目标,缺省为$0。

  index(s,t)函数返回目标字符串s中查询字符串t的首位置。length函数返回字符串s字符长度。

  match函数测试字符串s是否包含一个正则表达式r定义的匹配。

  split使用域分隔符fs将字符串s划分为指定序列a。

  sprint函数类似于printf函数,返回基本输出格式fmt的结果字符串exp。

  sub(r,s)函数将用s替代$0中最左边最长的子串,该子串被r匹配。

  sub(s,p)返回字符串s在位置p后的后缀。

  substr(s,p,n)同上,并指定子串长度为n。

  按照顺序,我们应当讲解awk程序设计的内容了,但在讲解之前,我们将用一些例子来对前面的知识进行回顾,这些例子都是在命令行中使用的,由此我们可以知道在命令行中使用awk是多么的方便。这样做的原因一方面是为下面的内容作铺垫,另一方面是介绍一些解决简单问题的方法,我们完全没有必要用复杂的方法来解决简单的问题----既然awk提供了较为简单的方法的话。

  例:显示文本文件mydoc匹配(含有)字符串"sun"的所有行。

  awk '/sun/' mydoc

  由于显示整个记录(全行)是awk的缺省动作,因此可以省略action项。

  例:下面是一个较为复杂的匹配的示例:

  awk '/[Ss]un/,/[Mm]oon/ ' myfile

  它将显示第一个匹配Sun或sun的行与第一个匹配Moon或moon的行之间的行,并显示到标准输出上。

  例:下面的示例显示了内置变量和内置函数length()的使用:

  awk 'length($0)>80 {print NR}' myfile

  该命令行将显示文本myfile中所有超过80个字符的行号,在这里,用0表示整个记录(行),同时,内置变量NR不使用标志符''。

  例:作为一个较为实际的例子,我们假设要对UNIX中的用户进行安全性检查,方法是考察/etc下的passwd文件,检查其中的passwd字段(第二字段)是否为"*",如不为"*",则表示该用户没有设置密码,显示出这些用户名(第一字段)。我们可以用如下语句实现:

  #awk -F: '$2!="*" {printf("%s no password!\n",$1)'} /etc/passwd

  在这个示例中,passwd文件的字段分隔符是“:”,因此,必须用-F:来更改默认的字段分隔符,这个示例中也涉及到了内置函数printf的使用。

  awk内置变量详细介绍

  awk提供了有很多内置变量,如果你经常使用awk处理文本,了解这些这些内置变量的使用是很有必要的。下面看一下这些内置变量的详细介绍。

  ARGC 命令行参数个数(不包括awk的选项和awk的程序内容)

  ARGIND 当前正在处理的ARGV中的文件的索引值(同时处理多个文件时会用到)

  ARGV 命令行参数序列数组,下标从0开始

  CONVFMT 数字转换格式,和C语言中的数字输出格式化类似, 默认为"%.6g"

  ENVIRON 当前系统的环境变量

  ERRNO 出错时的错误信息

  FIELDWIDTHS 以空格分隔的字段宽度,如果指定此变量,awk将会用指定的宽度替换变量FS指定的分隔符。看下面的例子:

  [LinuxSong@test /tmp]$ cat t

  abcdefasfa

  [LinuxSong@test /tmp]$ awk 'BEGIN {FIELDWIDTHS="2 3 4"}{print $1,$2,$3}' t

  ab cde fasf

  FILENAME 当前正在处理的文件名,该变量不能在BEGIN块中使用。

  FNR 当前处理的记录号

  FS 字段的分隔符,默认为空格。

  IGNORECASE 如果该变量设置为非0值,在进行字符串匹配时忽略大小写。

  NF 当前记录中的字段个数

  NR 已经读出的记录数

  OFMT 数字的输出格式

  OFS 输出的字段分隔符,默认为空格

  ORS 输出的记录分隔符,默认为新行

  RS 输入记录的分隔符,默认为新行

  RSTART 被match()函数匹配的字符串的起始位置,如果没有匹配则为0(从1开始)

  RLENGTH 被match()函数匹配的字符串的长度

  SUBSEP 数组中多个下标的分隔符,默认为"\034"

  如同其它程序设计语言一样,awk允许在程序语言中设置变量,事实上,提供变量的功能是程序设计语言的基本要求,不提供变量的程序设计语言本人还从未见过。

  awk 提供两种变量,一种是awk内置的变量,这前面我们已经讲过,需要着重指出的是,与后面提到的其它变量不同的是,在awk程序中引用内置变量不需要使用标志符""(回忆一下前面讲过的NR的使用)。awk提供的另一种变量是自定义变量。awk允许用户在awk程序语句中定义并调用自已的变量。当然这种变量不能与内置变量及其它awk保留字相同,在awk中引用自定义变量必须在它前面加上标志符""。与C语言不同的是,awk中不需要对变量进行初始化, awk根据其在awk中第一次出现的形式和上下文确定其具体的数据类型。当变量类型不确定时,awk默认其为字符串类型。这里有一个技巧:如果你要让你的 awk程序知道你所使用的变量的明确类型,你应当在在程序中给它赋初值。在后面的实例中,我们将用到这一技巧。

  运算与判断:

  作为一种程序设计语言所应具有的特点之一,awk支持多种运算,这些运算与C语言提供的几本相同:如+、-、*、/、%等等,同时,awk也支持C语言中类似++、--、+=、-=、=+、=-之类的功能,这给熟悉C语言的使用者编写awk程序带来了极大的方便。作为对运算功能的一种扩展,awk还提供了一系列内置的运算函数(如log、sqr、cos、sin等等)和一些用于对字符串进行操作(运算)的函数(如length、substr等等)。这些函数的引用大大的提高了awk的运算功能。

  作为对条件转移指令的一部分,关系判断是每种程序设计语言都具备的功能,awk也不例外。awk 中允许进行多种测试,如常用的==(等于)、!=(不等于)、>(大于)、=(大于等于)、>=(小于等于)等等,同时,作为样式匹配,还提供了~(匹配于)和!~(不匹配于)判断。

  作为对测试的一种扩充,awk也支持用逻辑运算符:!(非)、&&(与)、||(或)和括号()进行多重判断,这大大增强了awk的功能。本文的附录中列出了awk所允许的运算、判断以及操作符的优先级。

  流程控制语句是任何程序设计语言都不能缺少的部分。任何好的语言都有一些执行流程控制的语句。awk提供的完备的流程控制语句类似于C语言,这给我们编程带来了极大的方便。

  1、BEGIN和END:

  在awk 中两个特别的表达式,BEGIN和END,这两者都可用于pattern中(参考前面的awk语法),提供BEGIN和END的作用是给程序赋予初始状态和在程序结束之后执行一些扫尾的工作。任何在BEGIN之后列出的操作(在{}内)将在awk开始扫描输入之前执行,而END之后列出的操作将在扫描完全部的输入之后执行。因此,通常使用BEGIN来显示变量和预置(初始化)变量,使用END来输出最终结果。

  例:累计销售文件xs中的销售金额(假设销售金额在记录的第三字段):

  awk

  >'BEGIN { FS=":";print "统计销售金额";total=0}

  >{print 3;total=total+3;}

  >END {printf "销售金额总计:%.2f",total}' sx

  (注:>是shell提供的第二提示符,如要在shell程序awk语句和awk语言中换行,则需在行尾加反斜杠\)

  在这里,BEGIN预置了内部变量FS(字段分隔符)和自定义变量total,同时在扫描之前显示出输出行头。而END则在扫描完成后打印出总合计。

  2、流程控制语句

  awk提供了完备的流程控制语句,其用法与C语言类似。下面我们一一加以说明:

  2.1、if...else语句:

  格式:

  if(表达式)

  语句1

  else

  语句2

  格式中"语句1"可以是多个语句,如果你为了方便awk判断也方便你自已阅读,你最好将多个语句用{}括起来。awk分枝结构允许嵌套,其格式为:

  if(表达式1)

  {if(表达式2)

  }

  语句3

  else {if(表达式3)

  语句4

  语句5

  语句6

  当然实际操作过程中你可能不会用到如此复杂的分枝结构,这里只是为了给出其样式罢了。

  2.2、while语句

  格式为:

  while(表达式)

  语句

  2.3、do-while语句

  do

  {

  }while(条件判断语句)

  2.4、for语句

  格式为:

  for(初始表达式;终止条件;步长表达式)

  {语句}

  在awk 的 while、do-while和for语句中允许使用break,continue语句来控制流程走向,也允许使用exit这样的语句来退出。break 中断当前正在执行的循环并跳到循环外执行下一条语句。continue从当前位置跳到循环开始处执行。对于exit的执行有两种情况:当exit语句不在 END中时,任何操作中的exit命令表现得如同到了文件尾,所有模式或操作执行将停止,END模式中的操作被执行。而出现在END中的exit将导致程序终止。

  例:为了

  定义和调用用户自己的函数是几乎每个高级语言都具有的功能,awk也不例外,但原始的awk并不提供函数功能,只有在nawk或较新的awk版本中才可以增加函数。

  函数的使用包含两部分:函数的定义与函数调用。其中函数定义又包括要执行的代码(函数本身)和从主程序代码传递到该函数的临时调用。

  awk函数的定义方法如下:

  function 函数名(参数表){

  函数体

  在gawk中允许将function省略为func,但其它版本的awk不允许。函数名必须是一个合法的标志符,参数表中可以不提供参数(但在调用函数时函数名后的一对括号仍然是不可缺少的),也可以提供一个或多个参数。与C语言相似,awk的参数也是通过值来传递的。

  在awk 中调用函数比较简单,其方法与C语言相似,但awk比C语言更为灵活,它不执行参数有效性检查。换句话说,在你调用函数时,可以列出比函数预计(函数定义中规定)的多或少的参数,多余的参数会被awk所忽略,而不足的参数,awk将它们置为缺省值0或空字符串,具体置为何值,将取决于参数的使用方式。

  awk函数有两种返回方式:隐式返回和显式返回。当awk执行到函数的结尾时,它自动地返回到调用程序,这是函数是隐式返回的。如果需要在结束之前退出函数,可以明确地使用返回语句提前退出。方法是在函数中使用形如:return 返回值 格式的语句。

  例:下面的例子演示了函数的使用。在这个示例中,定义了一个名为print_header的函数,该函数调用了两个参数FileName和 PageNum, FileName参数传给函数当前使用的文件名,PageNum参数是当前页的页号。这个函数的功能是打印(显示)出当前文件的文件名,和当前页的页号。完成这个功能后,这个函数将返回下一页的页号。

  nawk

  >'BEGIN{pageno=1;file=FILENAME

  >pageno=print_header(file,pageno);#调用函数print_header

  >printf("当前页页号是:%d\n",pageno);

  >}

  >#定义函数print_header

  >function print_header(FileName,PageNum){

  >printf("%s %d\n",FileName,PageNum); >PageNum++;return PageNUm;

  >}' myfile

  执行这个程序将显示如下内容:

  myfile 1

  当前页页号是:2

  1.读取下一条记录:

  awk的next语句导致awk读取下一个记录并完成模式匹配,然后立即执行相应的操作。通常它用匹配的模式执行操作中的代码。next导致这个记录的任何额外匹配模式被忽略。

  2.简单地读取一条记录

  awk 的 getline语句用于简单地读取一条记录。如果用户有一个数据记录类似两个物理记录,那么getline将尤其有用。它完成一般字段的分离(设置字段变量0 FNR NF NR)。如果成功则返回1,失败则返回0(到达文件尾)。如果需简单地读取一个文件,则可以编写以下代码:

  例:示例getline的使用

  {while(getline==1)

  #process the inputted fields

  也可以使getline保存输入数据在一个字段中,而不是通过使用getline variable的形式处理一般字段。当使用这种方式时,NF被置成0,FNR和NR被增值。

  用户也可以使用getline"datafile"

  或

  printf("hello word!\n"

  >>"datafile"

  3. 输出记录

  可以通过print 和 printf 将结果输出:

  awk '{ print $1, $2 }' file

  4.输出到一个命令

  awk中允许用如下方式将结果输出到一个命令:

  |"sort-t','"

  awk与shell script混合编程

  因为awk可以作为一个shell命令使用,因此awk能与shell批处理程序很好的融合在一起,这给实现awk与shell程序的混合编程提供了可能。实现混合编程的关键是awk与shell script之间的对话,换言之,就是awk与shell script之间的信息交流:awk从shell script中获取所需的信息(通常是变量的值)、在awk中执行shell命令行、shell script将命令执行的结果送给awk处理以及shell scri