Board logo

标题: [推荐] awk gawk文章收集 [打印本页]

作者: 无奈何     时间: 2006-10-27 02:19    标题: [推荐] awk gawk文章收集

这是收集的 awk 与 gawk 的一些文章,资源及链接本帖添加补充。
作者: 无奈何     时间: 2006-10-27 02:19    标题: AWK Tutorial Guide

转贴注:原始链接[url]http://phi.sinica.edu.tw/aspac/reports/94/94011/[/url]
AWK Tutorial Guide
中央研究院计算中心
ASPAC计划
[email]aspac@phi.sinica.edu.tw[/email]
技术报告:94011
83年12月5日
Version:2.2


--------------------------------------------------------------------------------
版权声明
Contents
Preface
Overview of AWK
Why AWK
How to get AWK.
How AWK works.
How to Compute and Print Certain Fields
Selection by Text Content and by Comparsion
Arrays in AWK
Making Shell Command in an AWK Program
A Pratical Example
Redirecting Output to Files
Using System Resources
Execute AWK Programs.
Changing Field Separator & User Define Functions
Using getline to Input file
Multi-line Record
Getting Argument on Command Line
Writing Interactive Program in AWK
Recursive Program
Appendix A Patterns
Appendix B Actions
Appendix C Built-in Funtions
Appendix D Built-in Variables
Appendix E Regular Expression

--------------------------------------------------------------------------------

Preface 前言

有关本手册 :
        这是一本AWK学习指引, 其重点着重于 :   
           AWK 适于解决哪些问题 ?
           AWK 常见的解题模式为何  ?
为使读者快速掌握AWK解题的模式及特性, 本手册系由一些较具
代表性的范例及其题解所构成; 各范例由浅入深, 彼此间相互连贯,
范例中并对所使用的AWK语法及指令辅以必要的说明. 有关AWK的
指令, 函数,...等条列式的说明则收录于附录中, 以利读者往后撰写
程式时查阅. 如此编排, 可让读者在短时间内顺畅地学会使用AWK
来解决问题. 建议读者循着范例上机实习, 以加深学习效果.


读者宜先具备下列背景 :
[a.] UNIX 环境下的简单操作及基本概念.
      例如 : 档案编辑, 档案复制 及 pipe, I/O Redirection 等概念

[b.] C 语言的基本语法及流程控制指令.
(AWK 指令并不多, 且其中之大部分与 C语言中之用法一致, 本手册
中对该类指令之语法及特性不再加以繁冗的说明, 读者若欲深究,
可自行翻阅相关的 C 语言书籍)
    例如 : printf(), while()...
参考书目 :
本手册是以学习指引为主要编排方式, 读者若需要有关AWK介绍
详尽的参考书,可参考下列两本书 :

Alfred V. Aho, Brian W. Kernighan and Peter J. Weinberger,
The  AWK Programming Language'',
Addison-Wesley Publishing Company
Dale Dougherty, " sed & awk '', O`Reilly & Associates, Inc
  



--------------------------------------------------------------------------------

Overview of AWK
Why AWK
AWK 是一种程式语言. 它具有一般程式语言常见的功能.
因AWK语言具有某些特点, 如 : 使用直译器(Interpreter)不需先行
编译; 变数无型别之分(Typeless), 可使用文字当阵列的注标
(Associative Array)...等特色. 因此, 使用AWK撰写程式比起
使用其它语言更简洁便利且节省时间. AWK还具有一些内建
功能, 使得AWK擅于处理具资料列(Record), 栏位(Field)型
态的资料; 此外, AWK内建有pipe的功能, 可将处理中的资料
传送给外部的 Shell命令加以处理, 再将Shell命令处理后的
资料传回AWK程式, 这个特点也使得AWK程式很容易使用
系统资源.
   
由于AWK具有上述特色, 在问题处理的过程, 可轻易使用
AWK来撰写一些小工具; 这些小工具并非用来解决整个大问题,
它们只个别扮演解决问题过程的某些角色, 可藉由Shell所提供的
pipe将资料按需要传送给不同的小工具进行处理, 以解决整个
大问题. 这种解题方式, 使得这些小工具可因不同需求而被重覆
组合及使用(reuse); 也可藉此方式来先行测试大程式原型的可行性
与正确性, 将来若需要较高的执行速度时再用C语言来改写.
这是AWK最常被应用之处. 若能常常如此处理问题, 读者可以
以更高的角度来思考抽象的问题, 而不会被拘泥于细节的部份.
本手册为AWK入门的学习指引, 其内容将先强调如何撰写AWK程式,
未列入进一步解题方式的应用实例, 这部分将留待UNIX进阶手册中
再行讨论.
如何取得 AWK

一般的UNIX作业系统, 本身即附有AWK. 不同的UNIX作业系统
所附的AWK其版本亦不尽相同. 若读者所使用的系统上未附有AWK,
可透过 anonymous ftp 到下列地方取得 :
         phi.sinica.edu.tw:/pub/gnu
         ftp.edu.tw:/UNIX/gnu
         prep.ai.mit.edu:/pub/gnu

--------------------------------------------------------------------------------

How AWK works

为便于解释AWK程式架构, 及有关术语(terminology), 先以一个
员工薪资档(emp.dat ), 来加以介绍.

         A125  & Jenny  &100  &210   
         A341  & Dan    &110  &215
         P158  & Max    &130  &209
         P148  & John   &125  &220
         A123  & Linda  & 95  &210      
档案中各栏位依次为 员工ID, 姓名, 薪资率,及 实际工时. ID
中的第一码为部门识别码. ``A'',''P''分别表示``组装''及``包装''部门.
本小节着重于说明AWK程式的主要架构及工作原理, 并对一些重要
的名词辅以必要的解 释. 由这部分内容, 读者可体会出AWK语言
的主要精神及AWK与其它语程式言的差异处. 为便于说明, 以条列
方式说明于后.

名词定义

资料列: AWK从资料档上读取资料的基本单位.以上列档案
emp.dat为例, AWK读入的
   第一笔资料列是 "A125   Jenny  100  210"
   第二笔资料列是 "A341   Dan    110  215"
   一般而言, 一笔资料列相当于资料档上的一行资料.
   (参考 : 附录 B 内建变数``RS'' )

栏位(Field) : 为资料列上被分隔开的子字串.
          以资料列``A125   Jenny  100  210''为例,
第一栏 第二栏  第三栏 第四栏  ``A125''  ``Jenny''  100  210  
一般是以空白字元来分隔相邻的栏位. ( 参考 : 附录 D  内建
变数``FS'' )

如何执行AWK
于UNIX的命令列上键入诸如下列格式的指令: ( ``$''表Shell命令
列上的提示符号)
$awk 'AWK程式'  资料档档名  
则AWK会先编译该程式, 然后执行该程式来处理所指定的资料档.
       (上列方式系直接把程式写在UNIX的命令列上)
AWK程式的主要结构 :
      AWK程式中主要语法是 Pattern  { Actions}, 故常见之AWK
程式其型态如下 :
                Pattern1  { Actions1 }
                Pattern2  { Actions2 }
                ......
                Pattern3  { Actions3 }
   
  Pattern 是什么 ?
AWK 可接受许多不同型态的 Pattern. 一般常使用 ``关系判断式'
(Relational expres sion) 来当成 Pattern.
例如 :
    x  > 34 是一个Pattern, 判断变数 x 与 34 是否存在 大于 的关系.

   x == y 是一个Pattern, 判断变数 x 与变数 y 是否存在等于的关系.

    上式中 x  >34 , x == y 便是典型的Pattern.

    AWK 提供 C 语言中常见的关系运算元(Relational Operators) 如
     >, <,  >=,  <=, ==, !=.
    此外, AWK 还提供 ~ (match) 及 !~(not match) 二个关系运算元
(注一). 其用法与涵义如下:
若 A 表一字串, B 表一 Regular Expression   
        A  ~ B 判断 字串A 中是否 包含   能合于(match)B式样的
       子字串.
        A !~ B 判断 字串A 中是否 未包含 能合于(match)B式样的
       子字串.
         
        例如 :

  ``banana'' ~ /an/ 整个是一个Pattern.
    因为``banana''中含有可match /an/的子字串, 故此关系式
    成立(true),
    整个Pattern的值也是true.
相关细节请参考 附录 A Patterns, 附录 E Regular Expression
[注 一 :] 有少数AWK论着, 把 ~, !~ 当成另一类的 Operator,
并不视为一种 Relational Operator. 本手册中将这两个运算元
当成一种 Relational Operator.

Actions 是什么?
      Actions 是由许多AWK指令构成. 而AWK的指令与 C 语言中的
指令十分类似.
        例如 :
AWK的 I/O指令 :  print, printf( ),  getline..
AWK的 流程控制指令 :  if(...){..} else{..},  while(...){...}...

(请参考 附录 B --- ``Actions'' )
   

AWK 如何处理 Pattern { Actions } ?
      AWK 会先判断(Evaluate) 该 Pattern 之值, 若 Pattern 判断
(Evaluate)后之值为true(或不为0的数字,或不是空的字串), 则 AWK
将执行该 Pattern 所对应的 Actions.
      反之, 若 Pattern 之值不为 true, 则AWK将不执行该 Pattern
      所对应的 Actions.
      例如 :  若AWK程式中有下列两指令


              50   > 23  :        {print "Hello! The word!!" }
            "banana" ~ /123/  { print "Good morning !" }

AWK会先判断 50  >23 是否成立. 因为该式成立, 所以AWK将印出
"Hello! The word!!". 而另一 Pattern 为 "banana" ~/123/, 因为
"banana" 内未含有任何子字串可 match /123/, 该 Pattern 之值为            
false, 故AWK将不会印出 "Good morning !"

   AWK 如何处理{ Actions } 的语法?(缺少Pattern部分)
        有时语法 Pattern { Actions }中, Pattern 部分被省略,
        只剩 {Actions}.
        这种情形表示 ``无条件执行这个 Actions''.  

AWK 的栏位变数
AWK 所内建的栏位变数及其涵意如下 :
栏位变数  涵意  $0 为一字串, 其内容为目前 AWK 所读入的资料列.  $1 代表 $0 上第一个栏位的资料.  $2 代表 $0 上第二栏个位的资料.  ...  其余类推  

读入资料列时, AWK如何修正(update)这些内建的栏位变数

当 AWK 从资料档中读取一笔资料列时, AWK 会使用内建变数
$0 予以记录.
每当 $0 被异动时 (例如 : 读入新的资料列 或 自行变更 $0,...)
      AWK 会立刻重新分析 $0 的栏位情况, 并将 $0 上各栏位的资料
用 $1, $2, ..予以记录.


AWK的内建变数(Built-in Variables)
AWK 提供了许多内建变数, 使用者于程式中可使用这些变数
来取得相关资讯.常见的内建变数有 :
内建变数  涵意  NF (Number of Fields)为一整数, 其值表$0上所存在的栏位数目.  NR (Number of Records)为一整数, 其值表AWK已读入的资料列数目.  FILENAMEAWK正在处理的资料档档名.  
例如 : AWK 从资料档 emp.dat 中读入第一笔资料列
  "A125  Jenny  100  210" 之后, 程式中:
   $0  之值将是 "A125   Jenny  100  210"
   $1  之值为 "A125"    $2  之值为 "Jenny"
   $3  之值为 100       $4  之值为 210
   NF 之值为 4         $NF   之值为 210
   NR 之值为 1         FILENAME 之值为 ``emp.dat''

AWK的工作流程 :
       执行AWK时, 它会反复进行下列四步骤.
      
  自动从指定的资料档中读取一笔资料列.
  自动更新(Update)相关的内建变数之值. 如 :  NF, NR, $0...
  逐次执行程式中 所有 的 Pattern { Actions } 指令.
  当执行完程式中所有 Pattern { Actions } 时, 若资料档中还
        有未读取的资料, 则反覆执行步骤1到步骤4.

        AWK会自动重覆进行上述4个步骤, 使用者不须于程式中
撰写这个回圈 (Loop).


--------------------------------------------------------------------------------

列印档案中指定的栏位资料并加以计算

AWK 处理资料时, 它会自动从资料档中一次读取一笔记录, 并会
将该资料切分成一个个的栏位; 程式中可使用 $1, $2,... 直接取得
各个栏位的内容. 这个特色让使用者易于用 AWK 撰写 reformatter
来改变资料格式.

[ 范例 :] 以档案 emp.dat 为例, 计算每人应发工资并列印报表.
[ 分析 :] AWK 会自行一次读入一列资料, 故程式中仅需告诉
AWK 如何处理所读入的资料列.

      执行如下命令 : ( $ 表UNIX命令列上的提示符号 )
      
        awk '{ print  $2, $3 * $4  }' emp.dat
     执行结果如下 :
     荧幕出现  :

                 Jenny 21000
                 Dan 23650
                 Max 27170
                 John 27500
                 Linda 19950


     说 明 :  
        
UNIX命令列上, 执行AWK的语法为:
      awk  'AWK程式'  欲处理的资料档档名.
      本范例中的 程式部分 为 {print $2, $3 * $4}.
      把程式置于命令列时, 程式之前后须以  ' 括住.
emp.dat 为指定给该程式处理的资料档档名.
           
本程式中使用 :  Pattern { Actions } 语法.
Pattern Actions   print $2, $3 * $4  
Pattern 部分被省略, 表无任何限制条件. 故AWK读入每笔资料列
后都将无条件执行这个 Actions.
print为AWK所提供的输出指令, 会将资料输出到stdout(荧幕).
     print 的参数间彼此以 ``{ ,}'' 隔开, 印出资料时彼此间会以空白
     隔开.
(参考 附录 D  内建变数OFS)
将上述的 程式部分 储存于档案  pay1.awk 中. 执行命令时再指定AWK程式档 之档名. 这是执行AWK的另一种方式, 特别适用于程
式较大的情况, 其语法如下:
$awk -f AWK程式档名 资料档档名

故执行下列两命令,将产生同样的结果.

$awk -f  pay1.awk  emp.dat
$awk ' { print  $2, $3 * $4 } '  emp.dat
      
读者可使用``-f''参数,让AWK主程式使用其它仅含 AWK函数 的
档案中的函数
其语法如下:
awk  -f AWK主程式档名 -f AWK函数档名 资料档档名
    (有关 AWK 中函数之宣告与使用于 7.4 中说明)                             
AWK中也提供与 C 语言中类似用法的 printf() 函数. 使用
该函数可进一步控制资料的输出格式.

编辑另一个AWK程式如下, 并取名为  pay2.awk
            
{ printf("\%6s   Work hours: %3d  Pay: %5d\", $2,\$3, $3* $4) }

执行下列命令
         
$awk -f  pay2.awk   emp.dat
               
执行结果荧幕出现:
                Jenny   Work hours: 100 Pay: 21000
                   Dan   Work hours: 110 Pay: 23650
                   Max   Work hours: 130 Pay: 27170
                  John   Work hours: 125 Pay: 27500
                 Linda   Work hours:  95 Pay: 19950
        

选印合乎指定条件的记录
Pattern { Action }为AWK中最主要的语法. 若某Pattern之值为真则执行它后方的 Action. AWK中常使用``关系判断式'' (Relational Expression)来当成 Pattern.
AWK中除了>, <, ==, != ,...等关系运算元( Relational Operators )外,另外提供 ~(match),!~(Not Match) 二个关系运算元. 利用这两个运算元, 可判断某字串是否包含能符合所指定 Regular Expression 的子字串. 由于这些特性, 很容易使用AWK来撰写需要字串比对, 判断的程式. [ 范例 :] 承上例,
组装部门员工调薪5%,(组装部门员工之ID.系以``A''开头)
所有员工最后之薪资率若仍低于100, 则以100计.
撰写AWK程式列印新的员工薪资率报表.
[分析 ] : 这个程式须先判断所读入的资料列是否合于指定条件, 再进行某些动作.AWK中 Pattern { Actions } 的语法已涵盖这种 `` if ( 条件 ) { 动作} ''的架构. 编写如下之程式, 并取名 adjust1.awk
$1 ~ /^A.*/ { $3 *= 1.05 } $3<100 { $3 = 100 }

{ printf("%s %8s %d\n", $1, $2, $3)} 执行下列命令 :

$awk -f adjust1.awk emp.dat

结果如下 : 荧幕出现 :
A125 Jenny 105
A341 Dan 115
P158 Max 130
P148 John 125
A123 Linda 100
说 明 :

AWK的工作程序是: 从资料档中每次读入一笔资料列, 依序执行完程式中所有的 Pattern{ Action }指令 Pattern  Actions  
$1~/^A.*/  { $3 *= 1.05 }  
$3 < 100  { $3 = 100 }  
{printf("%s%8s%d\n",$1,$2,$3)}  
再从资料档中读进下一笔记录继续进行处理.
第一个 Pattern { Action }是: $1 ~ /^A.*/ { $3 *= 1.05 } $1 ~ /^A.*/ 是一个Pattern, 用来判断该笔资料列的第一栏是否包含%以``A''开头的子字串. 其中 /^A.*/ 是一个Regular Expression, 用以表示任何以``A''开头的字串. (有关 Regular Expression 之用法 参考 附录 E ).
Actions 部分为 $3 *= 1.05 $3 *= 1.05 与 $3 = $3 * 1.05 意义相同. 运算子``*='' 之用法则与 C 语言中一样. 此后与 C 语言中用法相同的运算子或语法将不予赘述.


第二个 Pattern { Actions } 是: $3 <100 {$3 = 100 } 若第三栏的资料内容(表薪资率)小于100, 则调整为100.
第三个 Pattern { Actions } 是: {printf("%s %-8s %d\n",$1, $2, $3 )} 省略了Pattern(无条件执行Actions), 故所有资料列调整后的资料都将被印出.


--------------------------------------------------------------------------------

AWK 中阵列的特色

AWK程式中允许使用字串当做阵列的注标(index). 利用
这个特色十分有助于资料统计工作.(使用字串当注标的阵列称为
Associative  Array)   
     首先建立一个资料档, 并取名为 reg.dat. 此为一学生注册的
资料档; 第一栏为学生姓名, 其后为该生所修课程.           
              Mary      O.S.         Arch.         Discrete
              Steve     D.S.         Algorithm    Arch.
              Wang    Discrete   Graphics     O.S.
              Lisa       Graphics   A.I.            
              Lily        Discrete   Algorithm   
AWK中阵列的特性

使用字串当阵列的注标(index).
使用阵列前不须宣告阵列名称及其大小.
例如 : 希望用阵列来记录 reg.dat 中各门课程的修课人数.
          这情况,有二项资讯必须储存 :
         (a) 课程名称, 如 : ``O.S.'',``Arch.''.. ,共有哪些课程事前
并不明确.
         (b)各课程的修课人数. 如 : 有几个人修``O.S.''
在AWK中只要用一个阵列就可同时记录上列资讯. 其方法如下 :
使用一个阵列 Number[ ]  :

以课程名称当 Number[ ] 的注标.
以 Number[ ] 中不同注标所对映的元素代表修课人数.

例如 :
有2个学生修 ``O.S.'', 则以 Number[``O.S.''] = 2 表之.
若修``O.S.''的人数增加一人,
则 Number[``O.S.''] = Number[``O.S.''] + 1
或 Number["O.S."]++ .
如何取出阵列中储存的资讯
以 C 语言为例, 宣告 int  Arr[100]; 之后, 若想得知 Arr[ ]中所
储存的资料, 只须用一个回圈, 如 :
for(i=0; i<00; i++) printf("%d\n", Arr[i]);

即可. 上式中 :
阵列 Arr[ ] 的注标 : 0, 1, 2,..., 99
阵列 Arr[ ] 中各注标所对应的值 : Arr[0], Arr[1],...Arr[99]
但 AWK 中使用阵列并不须事先宣告. 以刚才使用的 Number[ ] 而言,
程式执行前, 并不知将来有哪些课程名称可能被当成 Number[ ] 的
注标.
AWK 提供了一个指令, 藉由该指令AWK会自动找寻阵列中使用过
的所有注标. 以 Number[ ] 为例, AWK将会找到 ``O.S.'', ``Arch.''",...
使用该指令时, 须指定所要找寻的阵列, 及一个变数. AWK会使用
该的变数来记录从阵列中找到的每一个注标. 例如
for(course in Number){....}
指定用 course 来记录 AWK 从Number[ ] 中所找到
的注标. AWK每找到一个注标时, 就用course记录该注标之值且
执行{....}中之指令. 藉由这个方式便可取出阵列中储存的资讯.
(详见下例)

范例 : 统计各科修课人数,并印出结果.
          建立如下程式,并取名为 course.awk:
         {  for( i=2; i
      END{for(coursein Number)
      printf("\%-10s %d\n", course, Number[course] )
         }

  执行下列命令 :
  awk  -f   course.awk    reg.dat
  执行结果如下 :

                  Discrete    3
                  D.S.          1
                  O.S.          2
                  Graphics   2
                  A.I.           1
                  Arch.        2
                  Algorithm  2
                                       
      说  明 :
        
这程式包含二个Pattern { Actions }指令.
Pattern Actions  {for( i=2; i< NF; i++) Number[$i]++ }  END { for( course in Number) printf("\%-10s \%d\n", course, Number[course] )}  
  
第一个Pattern { Actions }指令中省略了Pattern 部分. 故随着
每笔资料列的读入其Actions部分将逐次无条件被执行.
以AWK读入第一笔资料 `` Mary  O.S.  Arch. Discrete" 为例,
因为该笔资料 NF = 4(有4个栏位), 故该 Action 的for Loop中
i = 2,3,4.
i $i 最初 Number[$i] Number[$i]++ 之后  2 ``O.S.''  AWK default Number[``O.S'']=0  1  3 ``Arch.''  AWK default Number[``Arch'']=0  1  4 ``Discrete''  AWK default Number[``Discrete'']=0  1   
第二个 Pattern { Actions }指令中
    * { END}为AWK之保留字, 为{ Pattern}之一种.
    * { END}成立(其值为true)的条件是 :[0.3cm]
     ``AWK处理完所有资料, 即将离开程式时.
      平常读入资料列时,  END并不成立, 故其后的Actions
并不被执行;
      唯有当AWK读完所有资料时, 该Actions才会被执行 ( 注意,
不管资料列有多少笔, END仅在最后才成立, 故该Actions仅被执行
一次.)
     { BEGIN} 与 { END} 有点类似, 是AWK中另一个保留的{Pattern}.
      唯一不同的是 :
``以 { BEGIN 为 Pattern 的 Actions} 于程式一开始执行时, 被执行
一次.''
NF 为AWK的内建变数, 用以表示AWK正处理的资料计列中,
所包含的栏位个数.
               
AWK程式中若含有以 $ 开头的自定变数, 都将以如下方式解释 :
以 i= 2 为例, $i = $2 表第二个栏位资料. ( 实际上, $  在 AWK 中
为一运算元(Operator), 用以取得栏位资料.)
--------------------------------------------------------------------------------
AWK 程式中使用 Shell 命令

AWK程式中允许呼叫Shell指令. 并提供pipe解决AWK与系统间
资料传递的问题. 所以AWK很容易使用系统资源. 读者可利用这个
特色来撰写某些适用的系统工具.
      范例 : 写一AWK程式来列印出线上人数.  
          将下列程式建档, 命名为 count.awk

       BEGIN  {
                    while ( "who" | getline ) n++
                    print n
                    }
       并执行下列命令 :
       awk { -f} count.awk
       执行结果将会印出目前线上人数
       说 明 :

AWK 程式并不一定要处理资料档. 以本例而言, 仅输入程式
档count.awk, 未输入任何资料档.
BEGIN 和 END 同为AWK中之种一 Pattern. 以 BEGIN 为
Pattern之Actions,只有在AWK开始执行程式,尚未开启任何输入
档前, 被执行一次.(注意: 只被执行一次 )
``{ |}'' 为 AWK 中表示 pipe的符号. AWK 把 pipe之前的字串
''who''当成Shell上的命令, 并将该命令送往Shell执行, 执行的结果
(原先应于荧幕印出者)则藉由pipe送进AWK程式中.                  
getline为AWK所提供的输入指令.
          其语法如下 :
语法 由何处读取资料 资料读入后置于  getline var < file  所指定的 file 变数 var(var省略时,表示置于$0)  getline var  pipe  变数 var(var省略时,表示置于$0)  getline var  见 注一  变数 var(var省略时,表示置于$0)  
注一 : 当 Pattern 为 BEGIN 或 END 时, getline 将由 stdin 读取资料,
否则由AWK正处理的资料档上读取资料.
        getline 一次读取一行资料,
                  若读取成功则return 1,
                  若读取失败则return -1,
                  若遇到档案结束(EOF), 则return 0;
             本程式使用 getline 所 return 的资料 来做为 while 判断
回圈停止的条件,某些AWK版本较旧,并不容许使用者改变 $0 之值.
这种版的 AWK 执行本程式时会产生 Error, 读者可于 getline 之后
置上一个变数 (如此, getline 读进来的资料便不会被置于 $0 ),
或直接改用gawk便可解决.


--------------------------------------------------------------------------------

AWK 程式的应用实例

本节将示范一个统计上班到达时间及迟到次数的程式.

这程式每日被执行时将读入二个档案 :
            员工当日到班时间的资料档 ( 如下列之 arr.dat )
            存放员工当月迟到累计次数的档案.
  当程式执行执完毕后将更新第二个档案的资料(迟到次数), 并列印
当日的报表.这程式将分成下列数小节逐步完成, 其大纲如下 :
     
[7.1] 于到班资料档 {arr.dat} 之前端增加一列抬头
"ID  Number    Arrvial Time", 并产生报表输出到档案today_rpt1 中''
< 在AWK中如何将资料输出到档案  >
[7.2]将 {today\_rpt1} 上之资料按员工代号排序, 并加注执行当日
              之日期; 产生档案 today_rpt2
             < AWK中如何运用系统资源及AWK中Pipe之特性 >
[7.3]< 将AWK程式包含在一个shell script档案中>
[7.4] 于 today_rpt2 每日报表上, 迟到者之前加上"*", 并加注当日
平均到班时间; 产生档案 today_rpt3

[7.5] 从档案中读取当月迟到次数, 并根据当日出勤状况更新迟到累计数.
< 使用者于AWK中如何读取档案资料  >
某公司其员工到勤时间档如下, 取名为 {arr.dat}. 档案中第一栏为
员工代号, 第二栏为到达时间. 本范例中, 将使用该档案为资料档.

                1034    7:26
                1025    7:27
                1101    7:32
                1006    7:45
                1012    7:46
                1028    7:49
                1051    7:51
                1029    7:57
                1042    7:59
                1008    8:01
                1052    8:05
                1005    8:12


--------------------------------------------------------------------------------

将资料直接输出到档案

AWK中并未提供如 C 语言中之fopen() 指令, 也未有fprintf()
档案输出之指令. 但AWK中任何输出函数之后皆可藉由使用与
UNIX 中类似的 I/O  Redirection , 将输出的资料 Redirect 到指定
的档案; 其符号仍为 > (输出到一个新产生的档案) 或 >> ( append
输出的资料到档案末端 ).

[例 :]于到班资料档 arr.dat 之前端增加一列抬头如下 :
"ID  Number    Arrival Time", 并产生报表输出到档案 today_rpt1中      

建立如下档案并取名为reformat1.awk

BEGIN  { print `` ID  Number  Arrival Time''  > ``today_rpt1''
              print ``==========================='' > ``today_rpt1''
             }  

             { printf("    %s  %s\n", $1,$2 )  > "today_rpt1" }      执行:

              $awk  -f reformat1.awk   arr.dat

       执行后将产生档案 today\_rpt1, 其内容如下 :
        ID  Number   Arrival Time
       ============================
           1034         7:26
           1025         7:27
           1101         7:32
           1006         7:45
           1012         7:46
           1028         7:49
           1051         7:51
           1029         7:57
           1042         7:59
           1008         8:01
           1052         8:05
           1005         8:12
      说 明 :

AWK程式中, 档案名称 today_rpt1 之前后须以" 括住, 表示
today_rpt1 为一字串常数. 若未以"括住, 则 today_rpt1 将
被AWK解释为一个变数名称.
在AWK中任何变数使用之前, 并不须事先宣告. 其初始值为空字串
(Null string) 或 0.因此程式中若未以 " 将 today_rpt1 括住,
则 today_rpt1 将是一变数, 其值将是空字串, 这会于执行时
造成错误(Unix 无法帮您开启一个以Null String为档名的档案).

* 因此在编辑 AWK程式时, 须格外留心. 因为若敲错变数名称,
  AWK在编译程式时会认为是一新的变数, 并不会察觉. 如此
  往往会造成 RuntimeError.

BEGIN 为AWK的保留字, 是 Pattern 的一种.
     以 BEGIN 为 Pattern 的 Actions 于AWK程式刚被执行尚未读取
     资料时被执行一次, 此后便不再被执行.
读者或许觉得本程式中的I/O Redirection符号应使用 `` >>''
(append)而非 `` >''.
   \index{ { >} } \index{ { >>} }
[*] 本程式中若使用 ``>'' 将资料重导到 today_rpt1, AWK
第一次执行该指令时会产生一个新档 today_rpt1, 其后再
执行该指令时则把资料append到today_rpt1档末, 并非每执行
一次就重开一个新档.
[*] 若采用">>"其差异仅在第一次执行该指令时, 若已存在
today_rpt1则 AWK 将直接把资料append在原档案之末尾.
[*] 这一点, 与UNIX中的用法不同.



--------------------------------------------------------------------------------

AWK 中如何利用系统资源

AWK程式中很容易使用系统资源. 这包括于程式中途叫用 Shell
命令来处理程式中的部分资料; 或于呼叫 Shell 命令后将其产生
之结果交回 AWK 程式(不需将结果暂存于某个档案). 这过程乃是
藉由 AWK 所提供的 pipe (虽然有些类似 Unix 中的 pipe, 但特性
有些不同),及一个从 AWK 中呼叫 Unix 的 Shell command 的语法
来达成.
[例 :] 承上题, 将资料按员工ID排序后再输出到档案 today_rpt2,
并于表头附加执行时的日期.
  分 析 :

AWK 提供与 UNIX 用法近似的 pipe, 其记号亦为 ``|''. 其用法及
涵意如下 :
AWK程式中可接受下列两语法 :
[a. 语法]  AWK output 指令 | ``Shell 接受的命令''
            ( 如 : print $1,$2 | "sort +1n" )  
     
[b. 语法] ``Shell 接受的命令'' |AWK input 指令      
             ( 如 : "ls " | getline)            
      
注 : AWK input 指令只有   getline 一个.
AWK output 指令有   print, printf() 二个.         

于 a 语法中, AWK所输出的资料将转送往 Shell, 由 Shell 的
命令进行处理.以上例而言, print 所印出的资料将经由 Shell 命令
``sort +1n'' 排序后再送往荧幕(stdout).
上列AWK程式中, ``print$1, $2'' 可能反覆执行很多次, 其印出的
结果将先暂存于 pipe 中,
等到该程式结束时, 才会一并进行 ``sort +1n''.
须注意二点 : 不论 print \$1, \$2 被执行几次,
       ``sort +1n'' 之 执行时间是  ``AWK程式结束时'',
       ``sort +1n'' 之 执行次数是  ``一次''.
   于 b 语法中, AWK将先叫用 Shell 命令. 其执行结果将经由
pipe 送入AWK程式以上例而言, AWK先令 Shell 执行 ``ls'',
Shell 执行后将结果存于 pipe, AWK指令 getline再从 pipe 中读取
资料.
使用本语法时应留心 : 以上例而言
AWK ``立刻''呼叫 Shell 来执行 ``ls'', 执行次数是一次.
getline 则可能执行多次(若pipe中存在多行资料).      
   除上列 a, b 二语法外, AWK程式中它处若出现像 "date", "cls", "ls"...
        等字串, AWK只当成一般字串处理之.


    建立如下档案并取名为 reformat2.awk

    #  程式 reformat2.awk  
    #  这程式用以练习AWK中的pipe
BEGIN {
             "date"  |  getline # Shell 执行 ``date''. getline 取得结果
             并以$0记录
              print " Today is " , $2, $3 >"today_rpt2"
              print "=========================" > "today_rpt2"
              print `` ID  Number Arrival Time'' >``today_rpt2''
              close( "today_rpt2" )
      }

   {printf( "%s  \%s\n", $1 ,$2 )"sort +2n >>today_rpt2"}
执行如下命令:
awk -f   reformat2.awk  arr.dat

执行后, 系统会自动将 sort 后的资料加( Append; 因为使用 `` >>'')
到档案 today_rpt2末端.  today_rpt2 内容如下 :
         Today is  Sep 17
     =========================
      ID Number   Arrival Time
         1005         8:12
         1006         7:45
         1008         8:01
         1012         7:46
         1025         7:27
         1028         7:49
         1029         7:57
         1034         7:26
         1042         7:59
         1051         7:51
         1052         8:05
         1101         7:32
              
  说 明 :

AWK程式由三个主要部分构成 :
     [ i.]    Pattern { Action} 指令
     [ ii.]   函数主体.  例如 : function double( x ){ return 2*x }
    (参考第11节 Recursive Program )
     [ iii.]  Comment       ( 以 # 开头识别之 )

AWK 的输入指令 getline, 每次读取一列资料. 若getline之后
未接任何变数, 则所读入之资料将以$0 纪录, 否则以所指定的变数
储存之.
[ 以本例而言] :
      执行 "date" | getline 后,
       $0 之值为 "Wed Aug 17 11:04:44 EAT 1994"
  当 $0 之值被更新时, AWK将自动更新相关的内建变数, 如 :
$1,$2,..,NF.故 $2 之值将为"Aug", $3之值将为"17".
  (有少数旧版之AWK不允许即使用者自行更新(update)$0之值,或者
update$0时,它不会自动更新 $1,$2,..NF. 这情况下, 可改用gawk,
或nawk. 否则使用者也可自行以AWK字串函数split()来分隔$0上
的资料)

  本程式中 printf() 指令会被执行12次( 因为有arr.dat中有12笔
资料), 但读者不用 担心资料被重复sort了12次. 当AWK结束该程式
时才会 close 这个 pipe , 此时才将这12笔资料一次送往系统,
并呼叫 "sort +2n >> today_rpt2" 处理之.
AWK提供另一个叫用Shell命令的方法, 即使用AWK函数
            system("shell命令")

        例如 :
              awk '
              BEGIN{
                      system("date  > date.dat")
                      getline  
script 中还可包含其它 Shell 命令, 如此更可增加执行过程的自动化.
建立一个简单的AWK程式 mydump.awk, 如下 :
                  {print}
这个程式执行时会把资料档的内容 print 到荧幕上( 与cat功用类似 ).
print 之后未接任何参数时, 表示 ``print $0''.
若欲执行该AWK程式, 来印出档案 today_rpt1 及 today_rpt2 的内容时,
必须于 UNIX 的命令列上执行下列命令 :

方式一 awk   -f  mydump.awk  today_rpt1 today_rpt2
方式二 awk ' print '  today_rpt1  today_rpt2第二种方式系将AWK
程式直接写在 Shell 的命令列上, 这种方式仅适合较短的AWK程式.
方式三 建立如下之 shell script, 并取名为 mydisplay,
            awk '     # 注意 , awk 与 ' 之间须有空白隔开
                 {print}                                   
                 ' $*  # 注意 , ' 与 $* 之间须有空白隔开

执行 mydisplay 之前, 须先将它改成可执行的档案(此步骤
往后不再赘述). 请执行如下命令:
$ chmod  +x mydisplay
往后使用者就可直接把 mydisplay 当成指令, 来display任何档案.
例如 :

         $ mydisplay  today_rpt1 today_rpt2

说 明 :

在script档案 mydisplay 中, 指令``awk''与第一个  '
之间须有空格(Shell中并无`` awk' ''指令).
第一个  ' 用以通知 Shell 其后为AWK程式.
第二个  ' 则表示 AWK 程式结束.
故AWK程式中一律以"括住字串或字元, 而不使用 ' ,
以免Shell混淆.

$* 为 shell script中之用法, 它可用以代表命令列上 ``mydisplay
之后的所有参数''.
例如执行 :
           $ mydisplay today_rpt1 today_rpt2
事实上 Shell 已先把该指令转换成 :
    awk '
             { print}
        '   today_rpt1  today_rpt2
本例中, $* 用以代表 ``today_rpt1 today_rpt2''. 在Shell的语法中,
可用 $1 代表第一个参数, $2 代表第二个参数. 当不确定命令列上的
参数个数时, 可使用 $* 表之.
AWK命令列上可同时指定多个资料档.  
  以awk  -f dump.awk  today_rpt1 today_rpt2hf 为例
  AWK会先处理today_rpt1, 再处理 today_rpt2. 此时若档案
  无法开启, 将造成错误.
  例如: 未存在档案"file_no_exist", 则执行 :
  awk  -f dump.awk  file_no_exit
  将产生Runtime Error(无法开启档案).
  但某些AWK程式 ``仅'' 包含以 BEGIN 为Pattern的指令. 执行这种
  AWK程式时, AWK并不须开启任何资料档.此时命令列上若指定
一个不存在的资料档,并不会产生 ``无法开启档案''的错误.(事实上
AWK并未开启该档案)
例如执行:
awk 'BEGIN {print "Hello,World!!"} ' file_no_exist
该程式中仅包含以 BEGIN 为 Pattern 之 Pattern {actions}, AWK
执行时并不会开启任何资料档; 故不会因不存在档案file_no_exit而
产生 `` 无法开启档案''的错误.  
AWK会将 Shell 命令列上AWK程式(或 -f 程式档名)之后的所有
字串, 视为将输入AWK进行处理的资料档档名.
若执行AWK的命令列上 ``未指定任何资料档档名'', 则将stdin视为
输入之资料来源, 直到输入end of file( Ctrl-D )为止.
读者可以下列程式自行测试, 执行如下命令 :
       $awk  -f dump.awk   (未接任何资料档档名)

       $ mydisplay         (未接任何资料档档名)
将会发现 : 此后键入的任何资料将逐行复印一份于荧幕上. 这情况
不是机器当机 ! 是因为AWK程式正处于执行中. 它正按程式指示,
将读取资料并重新dump一次; 只因执行时未指定资料档档名, 故AWK
便以stdin(键盘上的输入)为资料来源.
读者可利用这个特点, 设计可与AWK程式interactive talk的程式.



--------------------------------------------------------------------------------

改变 AWK 切割栏位的方式 & 使用者定义函数
      
AWK不仅能自动分割栏位, 也允许使用者改变其栏位切割方式以
适应各种格式之需要. 使用者也可自定函数, 若有需要可将该函数
单独写成一个档案,以供其它AWK程式叫用.

范例 : 承接 6.2 的例子, 若八点为上班时间, 请加注 ``*''于迟到记录
之前, 并计算平均上班时间.

分  析:

  因八点整到达者,不为迟到, 故仅以到达的小时数做判断是不够的;
仍应参考到达时的分钟数. 若 ``将到达时间转换成以分钟为单位'',
不仅易于判断是否迟到, 同时也易于计算到达平均时间.

到达时间($2)的格式为 dd:dd 或 d:dd; 数字当中含有一个 ":".
但文数字交杂的资料AWK无法直接做数学运算. (注: AWK中字串
"26"与数字26, 并无差异, 可直接做字串或数学运算, 这是AWK重要
特色之一. 但AWK对文数字交杂的字串无法正确进行数学运算).

解决之方法 :

方法一.
对到达时间($2) d:dd 或 dd:dd 进行字串运算,分别取出到达的小时数
及分钟数.
           
首先判断到达小时数为一位或两位字元,再呼叫函数分别截取分钟数
及小时数.

此解法需使用下列AWK字串函数:

length( 字串 ) : 传回该字串之长度.
substr( 字串,起始位置 ,长度 ) :传回从起始位置起, 指定长度
之子字串. 若未指定长度, 则传回起始位置到自串末尾之子字串.
所以:

小时数 = substr( $2, 1,  length($2) - 3 )
分钟数 = substr( $2, length($2) - 2 )

[方法二]  
                 改变输入列栏位的切割方式, 使AWK切割栏位后分别将
                 小时数及分钟数隔开于二个不同的栏位.
                 栏位分隔字元 FS (field seperator) 是AWK的内建变数,
                 其预设值是空白及tab. AWK每次切割栏位时都会先参考
                  FS 的内容. 若把":"也当成分隔字元, 则AWK 便能自动把
                 小时数及分钟数分隔成不同的栏位.
                 故令
   FS = "[ \t:]+" (注 : [ \t:]+ 为一Regular Expression )

Regular Expression 中使用中括号 [ ...  ] 表一字元集合,
用以表示任意一个位于两中括号间的字元.
故可用``[ \t:]''表示 一个 空白 , tab 或 ``:''
Regular Expression中使用 ``+'' 形容其前方的字元可出现一次
或一次以上.
故 ``[ \t:]+'' 表示由一个或多个 ``空白, tab 或 : '' 所组成的字串.

设定 FS =''[ \t:]+'' 后, 资料列如 : ``1034    7:26'' 将被分割成3个栏位.
第一栏 第二栏 第三栏 $1 $2 $3 1034 7 26
明显地, AWK程式中使用方法一比方法二更简洁方便. 本范例中采用
方法二,也藉此示范改变栏位切割方式之用途.

编写AWK程式 reformat3, 如下 :
awk '
BEGIN  {
            {FS= "[ \t:]+"  #改变栏位切割的方式            
            "date"  | getline  # Shell 执行 ``date''. getline 取得结果以$0纪录
             print " Today is " ,$2, $3  > "today_rpt3"
             print "=========================">"today_rpt3"
             print `` ID  Number Arrival Time'' > ``today_rpt3''
             close( "today_rpt3" )
            }

    {
   #已更改栏位切割方式, $2表到达小时数, $3表分钟数
arrival = HM_to_M($2, $3)
printf("  %s   %s:%s %s\n", $1,$2, $3
, arrival  > 480 ? "*": " "  ) | "sort +0n>>today_rpt3"
total += arrival
END {
            close("today_rpt3")   #参考本节说明 5
            close("sort +0n  >> today_rpt3")
           printf(" Average arrival time : %d:%d\n",
                  total/NR/60, (total/NR)%60 ) >> "today_rpt3"
         }
      function HM_to_M( hour, min ){
              return  hour*60 + min
             }
      ' $*
并执行如下指令 :
$ reformat3  arr.doc
   执行后,档案 today_rpt3 的内容如下:
        Today is  Sep 21
     =========================
      ID  Number Arrival Time
          1005      8:12  *
          1006      7:45   
          1008      8:01  *
          1012      7:46   
          1025      7:27   
          1028      7:49   
          1029      7:57   
          1034      7:26   
          1042      7:59   
          1051      7:51   
          1052      8:05  *
          1101      7:32   
      Average arrival time : 7:49
{verbatim}  
  说 明 :

AWK 中亦允许使用者自定函数. 函数定义方式请参考本程式,
function 为 AWK 的保留字.
HM_to_M( ) 这函数负责将所传入之小时及分钟数转换成
以分钟为单位.  使用者自定函数时, 还有许多细节须留心, 如
data scope,..
( 请参考 第十节 Recursive Program)

AWK中亦提供与 C 语言中相同的 Conditional Operator. 上式
printf()中使用arrival >480 ? "*" : " "}即为一例
若 arrival 大于 480 则return "*" , 否则return " ".
% 为AWK之运算子(operator), 其作用与 C 语言中之 % 相同
(取余数).
NR(Number of  Record) 为AWK的内建变数. 表AWK执行该程式
后所读入的纪录笔数.
  AWK 中提供的 close( )指令, 语法如下(有二种) :

close( filename )
close( 置于pipe之前的command )
为何本程式使用了两个 close( ) 指令 :

指令 close( "sort +2n >> today_rpt3" ), 其意思为 close 程式中
置于 "sort +2n >> today_rpt3 " 之前的 Pipe, 并立刻呼叫 Shell 来
执行"sort +2n  >> today_rpt3".
(若未执行这指令, AWK必须于结束该程式时才会进行上述动作;
则这12笔sort后的资料将被 append 到档案 today_rpt3 中
"Average arrival time : ..." 的后方)
因为 Shell 排序后的资料也要写到 today_rpt3, 所以AWK必须
先关闭使用中的today_rpt3 以利 Shell 正确将排序后的资料
append 到today_rpt3否则2个不同的 process 同时开启一
档案进行输出将会产生不可预期的结果.

读者应留心上述两点,才可正确控制资料输出到档案中的顺序.

指令 close("sort +0n >> today_rpt3")中字串 "sort +0n >> today_rpt3"
须与 pipe | 后方的 Shell Command 名称一字不差, 否则AWK将视为
二个不同的 pipe.
读者可于BEGIN{}中先令变数 Sys_call = "sort +0n  >> today_rpt3",
程式中再一律以 Sys_call 代替该字串.



--------------------------------------------------------------------------------

使用 getline 来读取资料

范 例 : 承上题,从档案中读取当月迟到次数, 并根据当日出勤状况
更新迟到累计数.(按不同的月份累计于不同的档案)

分 析:

   程式中自动抓取系统日期的月份名称, 连接上``late.dat'',
形成累计迟到次数的档案名称(如 : Jullate.dat,...), 并以变数
late_file纪录该档名.

累计迟到次数的档案中的资料格式为 :
                         员工代号(ID) 迟到次数
      例如, 执行本程式前档案 Auglate.dat 的内容为 :

                 1012 0
                 1006 1
                 1052 2
                 1034 0
                 1005 0
                 1029 2
                 1042 0
                 1051 0
                 1008 0
                 1101 0
                 1025 1
                 1028 0   



编写程式 reformat4.awk 如下:
   awk '
BEGIN {
           Sys_Sort = "sort +0n >> today_rpt4"
           Result = "today_rpt4"
           # 改变栏位切割的方式
          # 令 Shell执行``date''; getline 读取结果,并以$0纪录               
           FS = "[\t:]+"  
          "date" |  getline      
          print " Today is " , $2, $3 >Result
          print "=========================" > Result
          print `` ID  Number Arrival Time'' > Result
          close( Result )
# 从档按中读取迟到资料, 并用阵列cnt[ ]记录. 阵列cnt[ ]中以
员工代号为# 注标, 所对应的值为该员工之迟到次数.
late_file = $2 "late.dat"            
while( getline  < late_file >0 )  cnt[$1] = $2
close( late_file )
}
{  
  # 已更改栏位切割方式, $2表小时数,$3表分钟数
  arrival = HM_to_M($2, $3)  
  if( arrival > 480 ){
  mark = "*"  # 若当天迟到,应再增加其迟到次数, 且令
  mark 为''*''.cnt[$1]++ }   
  else   mark = " "
           
# message 用以显示该员工的迟到累计数, 若未曾迟到
message 为空字串
message = cnt[$1] ? cnt[$1] " times" : ""
   printf("%s%2d:%2d %5s %s\n", $1, $2, $3, mark,
message ) | Sys_Sort
         total += arrival
    }
END  {
           close( Result )
           close( Sys_Sort )
           printf(" Average arrival time : %d:%d\n", total/NR/60,
       (total/NR)%60 ) >> Result
           #将阵列cnt[ ]中新的迟到资料写回档案中
           for( any in cnt )
                 print any, cnt[any] > late_file
          }
   function HM_to_M( hour, min ){
                 return hour*60 + min
           }
  ' $*
执行后, today_rpt4 之内容如下 :

          Today is  Aug 17
    ================================
      ID Number  Arrival Time
         1005        8:12       * 1 times
         1006        7:45         1 times
         1008        8: 1       * 1 times
         1012        7:46         
         1025        7:27         1 times
         1028        7:49         
         1029        7:57         2 times
         1034        7:26         
         1042        7:59         
         1051        7:51         
         1052        8: 5       * 3 times
         1101        7:32         
     Average arrival time : 7:49

*
说  明 :

latefile 是一变数, 用以记录迟到次数的档案之档名.
latefile之值由两部分构成, 前半部是当月月份名称(由呼叫
"date"取得)后半部固定为"late.dat" 如 : Junlate.dat.

指令 getline <latefile 表由latefile所代表的档案中
读取一笔纪录, 并存放于$0.
若使用者可自行把资料放入$0, AWK会自动对这新置入 $0 的资料
进行栏位分割. 之后程式中可用$1, $2,..来表示该笔资料的第一栏,
第二栏,..,
              
(注: 有少数AWK版本不容许使用者自行将资料置于 $0, 遇此情况可改
用gawk或nawk)

执行getline指令时, 若成功读取纪录,它会传回1. 若遇到档案结束,
它传回0; 无法开启档案则传回-1.

利用 while( getline  < filename >0 ) {....}可读入档案中
的每一笔资料并予处理. 这是AWK中user自行读取档案资料的
一个重要模式.

阵列 late_cnt[ ] 以员工ID. 当注标(index), 其对应值表其迟到的
次数.

执行结束后, 利用 for(Variable in array ){..}之语法
for( any in late_cnt ) print any, late_cnt[any]> latefile
将更新过的迟到资料重新写回记录迟到次数之档案. 该语法于第5节
中曾有说明.



--------------------------------------------------------------------------------

处理 Multi-line 记录
AWK每次从资料档中只读取一笔Record, 进行处理. AWK系依照其内建变数 RS(Record Separator) 的定义将档案中的资料分隔成一笔一笔的Record. RS 的预设值是 "\n"(跳行符号), 故平常AWK中一行资料就是一笔 Record. 但有些档案中一笔Record涵盖了数行资料, 这种情况下不能再以 "\n" 来分隔Records. 最常使用的方法是相邻的Records之间改以 一个空白行 来隔开. 在AWK程式中, 令 RS = ""(空字串)后, AWK把会空白行当成来档案中Record的分隔符号. 显然AWK对 RS = "" 另有解释方式,简略描述如下, 当 RS = "" 时 :
数个并邻的空白行, AWK仅视成一个单一的Record Saparator. (AWK不会于两个紧并的空白行之间读取一笔空的Record)
AWK会略过(skip)档首或档末的空白行. 故不会因为档首或档末的空白行,造成AWK多读入了二笔空的资料.
请观察下例,首先建立一个资料档 week.rpt如下: 张长弓 GNUPLOT 入门 吴国强 Latex 简介 VAST-2 使用手册 mathematica 入门 李小华 AWK Tutorial Guide Regular Expression 该档案档首有数列空白行, 各笔Record之间使用一个或数个空白行隔开. 读者请细心观察, 当 RS = "" 时, AWK读取该资料档之方式. 编辑一个AWK程式档案 make_report如下: awk ' BEGIN { FS = "\n" RS = "" split( "一. 二. 三. 四. 五. 六. 七. 八. 九.", C\_Number, " " ) } { printf("\n%s 报告人 : %s \n",C_Number[NR],$1) for( i=2; i { >}= NF; i++) printf(" %d. %s\n", i-1, $i) } ' $ 执行 $ make_report week.rpt 荧幕产生结果如下: 一. 报告人 : 张长弓 1. GNUPLOT 入门 二. 报告人 : 吴国强 1. Latex 简介 2. VAST-2 使用手册 3. mathematica 入门 三. 报告人 : 李小华 1. AWK Tutorial Guide 2. Regular Expression 说明:
本程式同时也改变栏位分隔字元( FS= "\n"), 如此一笔资料中的每一行都是一个field. 例如 : AWK读入的第一笔 Record 为张长弓 GNUPLOT 入门 其中 $1 指的是"张长弓", $2 指的是"GNUPLOT 入门"
上式中的C\_Number[ ]是一个阵列(array), 用以记录中文数字. 例如 : C\_Number[1] = "一", C\_Number[2] = "二" 这过程使用AWK字串函数 split( ) 来把中文数字放进阵列 Number[ ]中. 函数 split( )用法如下 : split( 原字串, 阵列名称, 分隔字元(field separator) ) : AWK将依所指定的分隔字元(field separator)分隔原字串成一个个的栏位(field), 并以指定的 阵列 记录各个被分隔的栏位


--------------------------------------------------------------------------------

如何读取命令列上的参数

大部分的应用程式都容许使用者于命令之后增加一些选择性的参数.
执行AWK时这些参数大部分用于指定资料档档名, 有时希望在程式
中能从命令列上得到一些其它用途的资料. 本小节中将叙述如何在
AWK程式中取用这些参数.
    建立档案如下, 命名为 see_arg :
{
        awk '
        BEGIN {
   for( i=0; i<ARGC ; i++)
        print ARGV[i]  # 依次印出AWK所纪录的参数
}
'$*
执行如下命令 :
$  see_arg  first-arg  second-arg
结果荧幕出现 :
awk
     first-arg
     second-arg

  说明 :

ARGC, ARGV[ ] 为AWK所提供的内建变数.
   
   ARGC : 为一整数. 代表命令列上, 除了选项-v, -f 及其对应
   的参数之外所有参数的数目.
   ARGV[ ] : 为一字串阵列. ARGV[0],ARGV[1],...ARGV[ARGC-1].
   分别代表命令列上相对应的参数.
   
   例如, 当命令列为 :
   $awk  -vx=36  -f program1  data1  data2
    或
    awk '{ print $1 ,$2 }'  data1  data2
            其  ARGC    之值为  3
            ARGV[0] 之值为  "awk"
            ARGV[1] 之值为  "data1"
            ARGV[2] 之值为  "data2"

命令列上的 "-f program1", " -vx=36", 或程式部分 '{ print $1, $2}'
都不会列入 ARGC 及 ARGV[ ] 中.

AWK 利用 ARGC 来判断应开启的资料档个数.
但使用者可强行改变 ARGC; 当 ARGC 之值被使用者设为 1 时;
AWK将被蒙骗,误以为命令列上并无资料档档名, 故不会以 ARGV[1],
ARGV[2],..为档名来开档读取资料; 但于程式中仍可藉由 ARGV[1],
ARGV[2],..来取得命令列上的资料.

某一程式  test1.awk  如下 :
BEGIN{
            number = ARGC   #先用number 记住实际的参数个数.
            ARGC = 2 # 自行更改 ARGC=2, AWK将以为只有一个
                               资料档
                            # 仍可藉由ARGV[ ]取得命令列上的资料.
             for( i=2; i< number; i++) data[i] = ARGV[i]
          }

     ........

       于命令列上键入
$awk  -f test1.awk  data_file  apple  orange
执行时 AWK 会开启资料档 data_file 以进行处理. 不会开启以
apple,orange 为档名的档案(因为 ARGC 被改成2). 但仍可藉由
ARGV[2], ARGV[3]取得命令列上的参数 apple, orange
   
  可以下列命令来达成上例的效果.

$awk -f test2.awk -v data[2]="apple" -v data[3]="orange" data_file




--------------------------------------------------------------------------------

撰写可与使用者相互交谈的 AWK 程式

执行AWK程式时, AWK会自动由档案中读取资料来进行
处理, 直到档案结束.只要将AWK读取资料的来源改成键盘输入,
便可设计与AWK interactive talk 的程式.
本节将提供一个该类程式的范例.
[范例 :]  本节将撰写一个英语生字测验的程式, 它将印出中文字意,
再由使用者回答其英语生字.
首先编辑一个资料挡 test.dat (内容不拘,格式如下)

         apple   苹果
         orange  柳橙
         banana  香蕉
         pear    梨子
         starfruit 杨桃
         bellfruit 莲雾
         kiwi     奇异果
         pineapple 凤梨
         watermelon 西瓜


编辑AWK程式"c2e"如下:

awk '
BEGIN {
           while( getline < ARGV[1] ){ #由指定的档案中读取测验资料
           English[++n] = $1 # 最后, n 将表示题目之题数
           Chinese[n] = $2  
            }           
           ARGV[1] = "-"  # "-"表示由stdin(键盘输入)
           srand()           # 以系统时间为乱数启始的种子
           question( )      #产生考题
                        
            }
  

    {# AWK自动读入由键盘上输入的资料(使用者回答的答案)
        if($1 != English[ind] )
             print "Try again!"
        else{
        print "\nYou are right !! Press Enter to Continue --- "
        getline
        question( )#产生考题
    }  
  }

function question(){
        ind = int(rand( )* n) + 1 #以乱数选取考题
        system("clear")
        print " Press\"ctrl-d\" to exit"
        printf("\n%s ", Chinese[ind] " 的英文生字是: ")
  }
  '$*

执行时键入如下指令 :
   $c2e  test.dat
  荧幕将产生如下的画面:
  Press "ctrl-d " to exit
       莲雾 的英文生字是:
  若输入 bellfruit
程式将产生
    You are right !! Press Enter to Continue ---
}
  说 明 :


参数 test.dat (ARGV[1]) 表示储存考题的资料档档名.
AWK 由该档案上取得考题资料后, 将 ARGV[1] 改成 "-".
"-" 表示由 stdin(键盘输入) 资料. 键盘输入资料的结束符号 (End of file)
是 Ctrl-d. 当 AWK 读到 Ctrl-d 时就停止由 stdin 读取资料.
AWK的数学函数中提供两个与乱数有关的函数.
rand( ) : 传回介于 0与1之间的(近似)乱数值. 0


--------------------------------------------------------------------------------

使用 AWK 撰写 Recusive Program

AWK 中除了函数的参数列(Argument List)上的参数(Arguments)外,
所有变数不管于何处出现全被视为 Global variable. 其生命持续
至程式结束 --- 该变数不论在function外或 function内皆可使用,
只要变数名称相同所使用的就是同一个变数,直到程式结束.
因 Recusive 函数内部的变数, 会因它呼叫子函数(本身)而重覆使用,
      故撰写该类函数时, 应特别留心.
例如 : 执行
awk '
BEGIN
{
x = 35
y = 45
test_variable( x )
printf("Return to  main : arg1= %d, x= %d, y= %d, z= %d\n",
arg1, x, y, z)
}

  function test_variable( arg1 )
{
arg1++  # arg1 为参数列上的参数, 是local variable. 离开此函数
后将消失.
y ++       # 会改变主式中的变数 y
z = 55     # z 为该函数中新使用的变数, 主程式中变数 z 仍可被使用.
printf("Inside the function: arg1=%d,x=%d, y=%d, z=%d\n",
     arg1, x, y, z)
} '
结果荧幕印出

         Inside the function: arg1= 36,x= 35, y= 46, z= 55
         Return to main     : arg1= 0, x= 35, y= 46, z= 55

  由上可知 :

函数内可任意使用主程式中的任何变数.
函数内所启用的任何变数(除参数外), 于该函数之外依然可以使用.

此特性优劣参半, 最大的坏处是式中的变数不易被保护, 特别是
recursive呼叫本身, 执行子函数时会破坏父函数内的变数.
权变的方法是 : 在函数的 Argument list 上虚列一些 Arguments.
函数执行中使用这些虚列的 Arguments 来记录不想被破坏的资料,
如此执行子函数时就不会破坏到这些资料. 此外AWK 并不会检查,
呼叫函数时所传递的参数个数是否一致.
例如 : 定义 recursive function 如下 :
          function  demo( arg1 )# 最常见的错误例子
              ........
               for(i=1; i< 20 ; i++){
                 demo(x)
  # 又呼叫本身. 因为 i 是 global variable, 故执行完该子函数后
  # 原函数中的 i 已经被坏, 故本函数无法正确执行.
.......
        }
..........     
  }
可将上列函数中的 i 虚列在该函数的参数列上, 如此 i 便是一个
local variable, 不会因执行子函数而被破坏.
将上列函数修改如下:
function  demo( arg1,     i )
{
......
for(i=1; i< 20; i++)
     {
      demo(x)#AWK不会检查呼叫函数时, 所传递的参数个数是否一致
      .....
      }
}   
$0, $1,.., NF, NR,..也都是 global variable, 读者于 recusive function
中若有使用这些内建变数, 也应另外设立一些 local variable 来保存,
以免被破坏.
范例 :以下是一个常见的 Recursive 范例. 它要求使用者输入一串元素
(各元素间用空白隔开) 然后印出这些元素所有可能的排列.
编辑如下的AWK式, 取名为 permu
awk '
BEGIN
{
print "请输入排列的元素,各元素间请用空白隔开"
getline
permutation($0, "")  
printf("\n共 %d 种排列方式\n", counter)
}
function  permutation( main_lst, buffer, new_main_lst, nf, i, j )
{
  $0 = main_lst  # 把main_lst指定给$0之后AWK将自动进行
  栏位分割.
nf = NF            # 故可用 NF 表示 main_lst 上存在的元素个数.
#  BASE CASE : 当main_lst只有一个元素时.
     if( nf == 1)
    {     
     print buffer main_lst # buffer的内容连接(concate)上 main_lst 就
     counter++             # 是完成一次排列的结果
     return
    }
    # General Case : 每次从 main\_lst 中取出一个元素放到buffer中
    #  再用 main_lst 中剩下的元素 (new_main_lst) 往下进行排列
      else for( i=1; i< =nf ;i++)
      {
       $0 = main_lst # $0($1,$2,..$j,,)为Global variable已被坏, 故重新
                           #  把 main\_lst 指定给\$0, 令AWK再做一次栏位分割  
                 new_main_lst = ""
                 for(j=1; j< =nf; j++) # concate new_main_lst  
                    if( j != i )  new_main_lst = new_main_lst " " $j
                 permutation( new_main_lst, buffer " " $i  )
      }
  }
'$*
执行     $ permu
  荧幕上出现

              请输入排列的元素,各元素间请用空白隔开
  若输入 1 2 3 结果印出

              1 2 3
              1 3 2
              2 1 3
              2 3 1
              3 1 2
              3 2 1

             共 6 种排列方式


说 明 :

有些较旧版的AWK,并不容许使用者指定$0之值. 此时可改用
gawk, 或 nawk.
否则也可自行使用 split() 函数来分割 main_lst.

为避免执行子函数时破坏 new_main_lst, nf, i, j 故把这些变数
也列于参数列上. 如此, new_main_lst, nf, i, j 将被当成 local variable,
而不会受到子函数中同名的变数影响. 读者宣告函数时,参数列上
不妨将这些 ``虚列的参数'' 与真正用于传递资讯的参数间以较长
的空白隔开, 以便于区别.
AWK 中欲将字串concatenation(连接)时, 直接将两字串并置
即可(Implicit Operator).
       例如 :

    awk '
   BEGIN{
          A = "This "
          B = "is a "
          C = A B "key." # 变数A与B之间应留空白,
                                         否则''AB''将代表另一新变数.
            print C
                   }
           }
        结果将印出
        This is a key.

  AWK使用者所撰写的函数可再reuse, 并不需要每个AWK式中
都重新撰写.
将函数部分单读编写于一档案中, 当需要用到该函数时再以下列方式
include进来.
$ awk  -f 函数裆名 -f AWK主程式档名  资料档档名  



--------------------------------------------------------------------------------

Appendix A Patterns

AWK 藉由判断 Pattern 之值来决定是否执行其后所对应的
Actions.这里列出几种常见的 Pattern :


BEGIN  
BEGIN 为 AWK 的保留字, 是一种特殊的 Pattern.
BEGIN 成立(其值为true)的时机是 :
           ``AWK 程式一开始执行, 尚未读取任何资料之前.''
所以在 BEGIN { Actions } 语法中, 其 Actions 部份仅于程式
一开始执行时被执行一次. 当 AWK 从资料档读入资料列后,
BEGIN 便不再成立, 故不论有多少资料列, 该 Actions 部份仅被执行
一次.
一般常把 ``与资料档内容无关'' 与 ``只需执行ㄧ次'' 的部分置于该
Actions(以 BEGIN 为 Pattern)中.
例如 :
BEGIN {
        FS = "[ \t:]"   # 于程式一开始时, 改变AWK切割栏位的方式
        RS = ""         # 于程式一开始时, 改变AWK分隔资料列的方式
        count = 100     # 设定变数 count 的起始值
        print " This is a title line "  # 印出一行 title
      }
       ....... # 其它 Pattern { Actions } .....

有些AWK程式甚至''不需要读入任何资料列''. 遇到这情况可把整个
程式置于以 BEGIN 为 Pattern的 Actions 中.
例如 :
                  BEGIN {   print " Hello ! the Word ! " }

注意 :执行该类仅含 BEGIN { Actions } 的程式时, AWK 并不会开启
任何资料档进行处理.

END
END 为 AWK 的保留字, 是另一种特殊的 Pattern.
END 成立(其值为true)的时机与 BEGIN 恰好相反, 为 :
``AWK 处理完所有资料, 即将离开程式时''
平常读入资料列时, END并不成立, 故其对应的 Actions 并不被执行;
唯有当AWK读完所有资料时, 该 Actions 才会被执行
注意 : 不管资料列有多少笔, 该 Actions 仅被执行一次.

Relational Expression
使用像 `` A  Relation Operator B'' 的 Expression 当成 Pattern.  
当 A 与 B 存在所指定的关系(Relation)时, 该 Pattern 就算成立(true).
        例如 :
           length($0)< = 80  { print }
        上式中 { length($0)<= 80 是一个 Pattern, 当 $0(资料列)之长度
        小于等于 80 时该 Pattern 之值为true, 将执行其后的 Action
       (印出该资料列).   
        AWK 中提供下列 关系运算元(Relation Operator)

运算元 涵意  > 大于  < 小于  > = 大于或等于  <= 小于或等于  == 等于  != 不等于  ~ match  !~ not match  
上列关系运算元除~(match)与!~(not match)外与 C 语言中之
涵意一致.
~(match) 与!~(match) 在 AWK 之涵意简述如下 :
若 A 表一字串, B 表一 Regular Expression.
A ~B  判断 字串A 中是否 包含   能合于(match)B式样的子字串.
A !~B 判断 字串A 中是否 未包含 能合于(match)B式样的子字串.
     例如 :
$0 ~ /program[0-9]+\.c/  \{ print }
$0 ~/program[0-9]+\.c/ }整个是一个 Pattern, 用来判断$0(资料列)中
是否含有可 match /program[0-9]+\.c/ 的子字串, 若$0 中含有该类
字串, 则执行 print (印出该列资料).
Pattern 中被用来比对的字串为$0 时(如本例), 可仅以 Regular Expression
部分表之.
      故本例的 Pattern 部分
$0 ~/program[0-9]+\.c/ 可仅用/program[0-9]+\.c/表之     

     (有关 match 及 Regular Expression 请参考 附录 E )
Regular Expression
直接使用 Regular Expression 当成 Pattern; 此为 $0 ~
Regular Expression 的简写.
该 Pattern 用以判断 $0(资料列) 中是否含有 match 该 Regular Expression
的子字串; 若含有该成立(true) 则执行其对应的 Actions.
     例如 :  
/^[0-9]*$/  print "This line is a integer !"
与{ $0 ~/^[0-9]*$/ { print "This line is a integer !" } 相同
Compound Pattern
  之前所介绍的各种 Patterns, 其计算(evaluation)后结果为一逻辑值
(True or False).AWK 中逻辑值彼此间可藉由&&(and),||(or), !(not)
结合成一个新的逻辑值.故不同 Patterns 彼此可藉由上述结合符号
来结合成一个新的 Pattern. 如此可进行复杂的条件判断.
例 如 :
FNR > = 23 &&   FNR <=28  print "    " $0      }
     上式利用&& (and) 将两个 Pattern 求值的结果合并成一个逻辑值.
      该式 将资料档中 第23行 到 28行 向右移5格(先印出5个空白
      字元)后印出.
      ( FNR 为AWK的内建变数, 请参考 附录 D )

Pattern1 , Pattern2
遇到这种 Pattern, AWK 会帮您设立一个 switch(或flag).
当AWK读入的资料列使得 Pattern1 成立时, AWK 会打开(turn on)
这 switch.
当AWK读入的资料列使得 Pattern2 成立时, AWK 会关上(turn off)
这个 switch.
该 Pattern 成立的条件是 :
当这个 switch 被打开(turn on)时 (包括 Pattern1, 或 Pattern2 成立
的情况)例 如 :
FNR>= 23 && FNR< =28  { print "     " $0  }
     可改写为
     FNR == 23 ,  FNR == 28           { print "      " $0 }
说 明 :

当 FNR >= 23 时, AWK 就 turn on 这个 switch;
因为随着资料列的读入, AWK不停的累加 FNR.
当 FNR = 28 时, Pattern2  FNR == 28 便成立, 这时 AWK
会关上这个 switch.
当 switch 打开的期间, AWK 会执行 ``print "     " $0''

( FNR 为AWK的内建变数, 请参考 附录 D )


--------------------------------------------------------------------------------

Appendix B Actions

Actions 是由下列指令(statement)所组成 :

  expression ( function calls, assignments..)
  print expression-list
  printf( format, expression-list)
  if( expression ) statement [else statement]
  while( expression ) statement
  do statement while( expression)
  for( expression; expression; expression) statement
  for( variable in array) statement
  delete
  break
  continue
  next
  exit [expression]
  statement

AWK 中大部分指令与 C 语言中的用法一致, 此处仅介绍较为常用
或容易混淆之指令的用法.

流程控制指令

if 指令
     语法
           if (expression) statement1 [else statement2 ]
       范例 :

  if( $1> 25 )
       print "The 1st field is larger than 25"
  else print "The 1st field is not larger than 25"

(a)与 C 语言中相同, 若 expression 计算(evaluate)后之值不为 0
或空字串, 则执行 statement1; 否则执行 statement2.
(b)进行逻辑判断的expression所传回的值有两种, 若最后的逻辑值
为true, 则传回1, 否则传回0.
(c)语法中else statement2 以[ ] 前后括住表示该部分可视需要而
予加入或省略.

while 指令
     语法 :
             while( expression ) statement
     范例 :

             while( match(buffer,/[0-9]+\.c/ ) ){
             print "Find :" substr( buffer,RSTART, RLENGTH)
             buff = substr( buffer, RSTART + RLENGTH)
        }

上列范例找出 buffer 中所有能合于(match) /[0-9]+.c/(数字
之后接上 ``.c''的所有子字串).
范例中 while 以函数 match( )所传回的值做为判断条件. 若buffer
中还含有合于指定条件的子字串(match成功), 则 match()函数传回1,
while 将持续进行其后之statement.
do-while 指令
     语法 :
              do statement while(expression)   
     范例 :

  do{
          print "Enter y or n ! "
          getline data <  "-"

     } while( data !~ /^[YyNn]$/)

(a) 上例要求使用者从键盘上输入一个字元, 若该字元不是
Y, y, N, 或 n则会不停执行该回圈, 直到读取正确字元为止.
(b)do-while 指令与 while 指令 最大的差异是 : do-while 指令会先执行
statement而后再判断是否应继续执行. 所以, 无论如何其 statement 部分
至少会执行一次.

*
for Statement 指令(一)
     语法 :
             for(variable in  array ) statement
     范例 : 执行下列命令

awk '
      BEGIN{
             X[1]= 50; X[2]= 60; X["last"]= 70
             for( any in X )
                printf("X[%d] = %d\n", any, X[any] )
           }'

结果印出 :
             X[2] = 60
             X[last] = 70
             X[1] = 50

(a)这个 for 指令, 专用以搜寻阵中所有的index值, 并逐次使用所指定
的变数予以纪录. 以本例而言, 变数 any 将逐次代表 2, 1,及"last".
(b)以这个 for 指令, 所搜寻出的index之值彼此间并无任何次续关系.
(c)第7节 Arrays in AWK 中有该指令的使用范例, 及解说.

for Statement 指令(二)

     语法 :
             for(expression1; expression2; expression3) statement  
范例 :

    for(i=1; i< =10; i++)  sum = sum +  i

说明 :
(a)上列范例用以计算 1 加到 10 的总合.
(b)expression1  常用于设定该 for 回圈的起始条件, 如上例中的 i=1
    expression2 用于设定该回圈的停止条件, 如上例中的 i<= 10
    expression3 常用于改变 counter 之值, 如上例中的 i++
break 指令
  break 指令用以强迫中断(跳离) for, while, do-while 等回圈.
  范例 :
while(  getline < "datafile" > 0 )
{
        if( $1 == 0 )      # 所读取的资料置于 $0
             break         # AWK立刻把 $0 上新的栏位资料
        else               # 指定给 $1, $2, ...$NF
        print $2 / $1
}

上例中, AWK 不断地从档案 {datafile}中读取资料, 当$1等于0时,
就停止该执行回圈.
continue 指令
回圈中的 statement 进行到一半时, 执行 continue 指令来掠过回圈
中尚未执行的statement.
     范例 :

       for( index in X_array)
           {
             if( index !~ /[0-9]+/ )  continue
             print "There is a digital index", index
           }
上例中若 index 不为数字则执行 continue, 故将掠过(不执行)其后
的指令.
需留心 continue 与 break 的差异 : 执行 continue 只是掠过其后
未执行的statement, 但并未跳离开该回圈.
next 指令
执行 next 指令时, AWK 将掠过位于该指令(next)之后的所有指令
(包括其后的所有Pattern { Actions }), 接着读取下一笔资料列,
继续从第一个 Pattern {Actions}
执行起.
     范例 :

/^[ \t]*$/  {  print "This is a blank line! Do nothing here !"
               next
            }
$2 != 0   { print $1, $1/$2 }

上例中, 当 AWK 读入的资料列为空白行时( match /\^{}[\]*$/ )
除列印讯息外且执行 next, 故 AWK 将掠过其后的指令, 继续读取
下一笔资料, 从头(第一个 Pattern \{ Actions \})执行起.

*
exit 指令

执行 exit 指令时, AWK将立刻跳离(停止执行)该AWK程式.

AWK 中的 I/O 指令

printf 指令
该指令与 C 语言中的用法相同, 可藉由该指令控制资料输出时
的格式.
语法 :
         printf("format", item1, item2,.. )
  范 例 :

      id = "BE-2647";  ave = 89
     printf("ID# : %s   Ave Score : %d\n", id, ave)


(a)结果印出 :
    ID# :BE-647  Ave Score : 89
(b)format 部分系由 一般的字串(String Constant) 及 格式控制字元
(Formatcontrol letter, 其前会加上一个\%字元)所构成. 以上式为例
"ID# : " 及 "  Ave Score : " 为一般字串. %s 及 %d 为格式控制字元.
(c)印出时, 一般字串将被原封不动地印出. 遇到格式控制字元时,
则依序把 format后方之 item 转换成所指定的格式后印出.
(d)有关的细节, 读者可从介绍 C 语言的书籍上得到较完整的介绍.
(e)print 及 printf 两个指令, 其后可使用>或< > 将输出到stdout
的资料 Redirct到其它档案, 7.1 Redirect Output to Files 中有完整的
范例说明.
print 指令
范 例 :

    id = "BE-267";  ave = 89
    print "ID# :", id, "Ave Score :"ave

(a)结果印出 :
    ID# : BE-267  Ave Score :89   
(b)print 之后可接上字串常数(Constant String)或变数. 它们彼此间
可用``,'' 隔开.
(c)上式中, 字串 "ID# :" 与变数 id 之间使用``,''隔开, 印出时两者之间
会以自动 OFS(请参考 D 内建变数 OFS) 隔开. OFS 之值一般内定为
"一个空白字元"
(d)上式中字串 "Ave Score :" 与变数ave之间并未以``,''隔开, AWK
会将这两者先当成字串concate在一起(变成``Ave Score :89")后,
再予印出
(e)print 及 printf 两个指令, 其后可使用> 或> 将输出到stdout的
资料 Redirct到其它档案, 7.1 Redirect Output to Files 中有完整的
范例说明.
getline 指令
语法 由何处读取资料 资料读入后置于  getline var> file 所指定的 file 变数 var(var省略时,表示置于$0)  | getline var  pipe  变数 var(var省略时,表示置于$0)  getline var  见 注一  变数 var(var省略时,表示置于$0)  
注一 : 当 Pattern 为 BEGIN 或 END 时, getline 将由 stdin 读取资料,
否则由AWK正处理的资料档上读取资料.
   
getline 一次读取一行资料,
              若读取成功则return 1,
              若读取失败则return -1,
              若遇到档案结束(EOF), 则return 0
close  指令
该指令用以关闭一个开启的档案, 或 pipe(见下例)
范 例 :

awk '
BEGIN {  print "ID #   Salary" > "data.rpt" }   

      {  print $1 , $2 * $3  | "sort +0n > data.rpt" }   

END{  close( "data.rpt" )
         close( "sort +0n > data.rpt" )
         print " There are", NR, "records processed."
        }

说 明 :

(a)上例中, 一开始执行 print "ID #   Salary" > "data.rpt" 指令来
印出一行抬头. 它使用 I/O Redirection( > )将资料转输出到data.rpt,
故此时档案 {data.rpt} 是处于 Open 状态.
(b)指令 print $1, $2 * $3 不停的将印出的资料送往 pipe(|), AWK
于程式将结束时才会呼叫 shell 使用指令 "sort +0n > data.rpt"
来处理 pipe 中的资料; 并未立即执行, 这点与 Unix 中pipe的用法
不尽相同.

(c)最后希望于档案 {data.rpt}之``末尾''处加上一行 "There are.....".
但此时, Shell尚未执行 "sort +0n > data.rpt" 故各资料列排序后
的 ID 及 Salary 等资料尚未写入data.rpt. 所以得命令 AWK
提前先通知 Shell 执行命令 "sort +0n > data.rpt" 来处理 pipe
中的资料. AWK中这个动作称为 close pipe. 系由执行 close
( "shell command" )来完成. 需留心 close( )指令中的 shell command
需与``|''后方的 shell command 完全相同(一字不差), 较佳的方法是
先以该字串定义一个简短的变数, 程式中再以此变数代替该
shell command   
(d)为什么要执行 close("data.rpt") ?  因为 sort 完后的资料也将写到
data.rpt,
而该档案正为AWK所开启使用(write)中, 故AWK程式中应先关闭
data.rpt. 以免造成因二个 processes 同时开启一个档案进行
输出(write)所产生的错误.
system 指令   
该指令用以执行 Shell上 的 command.
范 例 :

    DataFile = "invent.rpt"
    system( "rm " DataFile )  

说明 :

(a)system("字串")指令接受一个字串当成Shell的命令. 上例中, 使用
一个字串常数"rm " 连接(concate)一个变数 DataFile 形成要求 Shell
执行的命令.Shell 实际执行的命令为 ``rm invent.rpt''.  


*
``|'' pipe指令
``|'' 配合 AWK 输出指令, 可把 output 到 stdout 的资料继续转送给
Shell 上的令一命令%
当成input的资料.
  ``|''  配合 AWK getline 指令, 可呼叫 Shell 执行某一命令, 再以 AWK
的 getline 指令将该命令的所产生的资料读进 AWK 程式中.
范 例 :

   { print $1, $2 * $3  | "sort +1n > result" }      
  
    "date" |  getline  Date_data

读者请参考 7.2 Using System Resources 其中有完整的范例说明.        

AWK 释放所占用的记忆体的指令
AWK 程式中常使用阵列(Array)来记忆大量资料. delete 指令便是用来
释放阵列中的元素所所占用的记忆空间.
范 例 :

    for( any in X_arr )  
             delete X_arr[any]

读者请留心, delete 指令一次只能释放阵列中的一个``元素''.
AWK 中的数学运算元(Arithmetic Operators)
    +(加), -(减), *(乘), /(除), %(求余数), ^(指数) 与 C 语言中用法
相同

AWK 中的 Assignment Operators
    =, +=, -=, *= , /=, %=, ^=
    x += 5 的意思为 x = x + 5, 其余类推.

AWK 中的 Conditonal  Operator
语 法 :
判断条件 ? value1 : value2
若 判断条件 成立(true) 则传回 value1, 否则传回 value2.

AWK 中的逻辑运算元(Logical Operators)
&&( and ), ||or, !(not)
  Extended Regular Expression 中使用 ``|'' 表示 or 请勿混淆.

AWK 中的关系运算元(Relational Operators)
>, >=, <, < =, ==, !=, ~, !~

AWK 中其它的运算元
    +(正号), -(负号),  ++(Increment Operator), - -(Decrement Operator)
AWK 中各运算元的运算优先顺序( Precedence )
(按优先高低排列)   

    $       (栏位运算元, 例如 : i=3; $i表示第3栏)
    ^       (指数运算)
    + ,- ,! (正,负号,及逻辑上的 not)
    * ,/ ,% (乘,除,余数)
    + ,-    (加,减)  
    >, >  =,< , < =, ==, != (大于,大于等于,...,等关系符号)
    ~, !~   (match, not match)
    &&      (逻辑上的 and)
    ||      (逻辑上的 or )
    ? :     (Conditional Operator)
    = , +=, -=,*=, /=, %=, ^= (一些指定Assignment运算元)



--------------------------------------------------------------------------------

AWK 的内建函数 Built-in Functions

(一). 字串函数

index( 原字串, 找寻的子字串 ):
若原字串中含有欲找寻的子字串,则传回该子字串在原字串中第一次
出现的位置,若未曾出现该子字串则传回0.
例如执行 :
$awk  'BEGIN{ print index("8-12-94","-" }'
结果印出 2
   
length( 字串 ) : 传回该字串的长度.
例如执行 :  
awk  'BEGIN { print length("John") '}
结果印出  4
match( 原字串, 用以找寻比对的 Regular Expression :
AWK会在原字串中找寻合乎Regular Expression的子字串. 若合乎
条件的子字串有多个, 则以原字串中最左方的子字串为准.
AWK找到该字串后会依此字串为依据进行下列动作:

设定AWK内建变数 RSTART, RLENGTH :
          RSTART &=  合条件之子字串在原字串中之位置.
                 &=  0 ; 若未找到合条件的子字串.
          RLENGTH &= 合条件之子字串长度.
                  &= -1 ; 若未找到合条件的子字串.
传回 RSTART 之值.
例如执行 :
    awk ' BEGIN {
                 match( "banana", /(an)+/ )
                 print RSTART, RLENGTH
               }
          '       
       执行结果印出 2  4

split( 原字串, 阵列名称, 分隔字元(field separator):
AWK将依所指定的分隔字元(field separator)来分隔原字串成
一个个的栏位(field),并以指定的阵列记录各个被分隔的栏位.

     例如 :
           ArgLst = "5P12p89"
            split( ArgLst, Arr, /[Pp]/)
     执行后   Arr[1]=5,  Arr[2]=12,  Arr[3]=89
sprintf( 列印之格式, 列印之资料, 列印之资料,...)
该函数之用法与AWK或C的输出函数printf()相同. 所不同的是
sprintf()会将要求印出的结果当成一个字串传回
一般最常使用sprintf()来改变资料格式. 如: x 为一数值资料, 若欲
将其变成一个含二位小数的资料,可执行如下指令 :
  x = 28
  x = sprintf("%.2f",x)}
执行后 x = "28.00"

sub( 比对用的 Regular Expression}, 将替换的新字串, 原字串 )
sub( )将原字串中第一个(最左边)合乎所指定的Regular Expression
的子字串改以新字串取代.

第二个参数"将替换的新字串"中可用"&"来代表"合于条件的
子字串"承上例,执行下列指令:
A = "a6b12anan212.45an6a"
         sub( /(an)+[0-9]*/, "[&]", A)
          结果印出ab12[anan212].45an6a
sub()不仅可执行取代(replacement)的功用,当第二个参数为
空字串("")时,sub()所执行的是``去除指定字串''的功用.
藉由 sub()与 match()的搭配使用,可逐次取出原字串中合乎
指定条件的所有子字串.
例如执行下列程式:
awk '
         BEGIN {
               data = "p12-P34 P56-p61"
              while( match( data ,/[0-9]+/) >0) {
                 print substr(data,{ RSTART, RLENGTH })
                 sub(/[0-9]+/,"")
                 }
              }
          ' $*  }
      结果印出 :
                12
                34
                56
                61
  sub( )中第三个参数(原字串)若未指定,则其预设值为$0.
  可用 sub( /[9-0]+/,"digital" ) 表示 sub(/[0-9]+/,"digital",$0 )

gsub( 比对用的 Regular Expression}, 将替换的新字串, 原字串)
这个函数与 sub()一样,同样是进行字串取代的函数. 唯一不同点是

     gsub()会取代所有合条件的子字串.
     gsub()会传回被取代的子字串个数.

   请参考 sub().
substr( 字串,起始位置 [,长度] ):
传回从起始位置起,指定长度之子字串. 若未指定长度,则传回起始
位置到自串末尾之子字串.
执行下例
       awk {' BEGIN{ print  substr( "User:Wei-Lin Liu", 6)}
     结果印出
        Wei-Lin Liu
(二). 数学函数

int(x) : 传回x的整数部分(去掉小数).
            例如 :
                int(7.8) 将传回 7
                int(-7.8) 将传回 -7

sqrt(x) : 传回x的平方根.
            例如 :
            sqart(9) 将传回 3
            若 x 为负数,则执行 sqrt(x)时将造成 Run Time Error

exp(x) : 将传回e的x次方.
            例如 :
            exp(1) 将传回 2.71828
            
log(x) : 将传回x以e为底的对数值.
            例如 :
                 log(e) = 1
            若 x< 0 ,则执行 sqrt(x)时将造成 Run Time Error.

sin(x) : x 须以径度量为单位,sin(x)将传回x的sin函数值.
cos(x) : x 须以径度量为单位,cos(x)将传回x的cos函数值
atan2(y,x) : 传回 y/x 的tan反函数之值,传回值系以径度量为单位.
rand( ) : 传回介于 0与1之间的(近似)乱数值; 0 < rand()<1.
除非使用者自行指定rand()函数起始的seed,否则每次执行AWK
程式时,  rand()函数都将使用同一个内定的seed,来产生乱数.
srand(x) : 指定以x为rand( )函数起始的seed.
若省略了x,则AWK会以执行时的日期与时间为rand()函数起始的seed.



--------------------------------------------------------------------------------

AWK 的内建变数 Built-in Variables

因内建变数的个数不多, 此处按其相关性分类说明, 并未按其字母
顺序排列.

ARGC 表命令列上除了选项 -F, -v, -f 及其所对应的参数之外
的所有参数的个数.若将``AWK程式''直接写于命令列上,
则 ARGC 亦不将该``程式部分''列入计算.
ARGV 一个阵列用以记录命令列上的参数.
  例 : 执行下列命令
  $awk  -F\t  -v a=8 -f prg.awk  file1.dat file2.dat
  或
  $awk  -F\t  -v a=8 '{ print $1 * a }' file1.dat file2.dat
         }
  执行上列任一程式后
                    ARGC    =  3
                    ARGV[0] = "awk"
                    ARGV[1] = "file1.dat"
                    ARGV[2] = "file2.dat"
   读者请留心 : 当 ARGC = 3 时, 命令列上仅指定 2 个资料档.
   注 :
        -F\t 表示以 tab 为栏位分隔字元 FS(field seporator).
        -v a=8 是用以 initialize 程式中的变数 a.
FILENAME 用以表示目前正在处理的资料档档名.
*
FS   栏位分隔字元.
$0   表示目前AWK所读入的资料列.
$1,$2..分别表示所读入的资料列之第一栏, 第二栏,..
(参考下列说明)
当AWK读入一笔资料列  ``A123  8:15'' 时,会先以$0 记载.
故 $0 = "A123  8:15"
若程式中进一步使用了 $1, $2.. 或 NF 等内建变数时, AWK才会
自动分割 $0.
以便取得栏位相关的资料. 切割后各个栏位的资料会分别以
$1, $2, $3...予以记录.
AWK内定(default)的 栏位分隔字元(FS) 为 空白字元(及tab).
以本例而言, 读者若未改变 FS, 则分割后 :
第一栏($1)="A123",  第二栏($2)="8:15".
使用者可用 Regexp 自行定义 FS. AWK每次需要分割资料列时,
会参考目前FS之值.
   例如 :
令 FS = "[ :]+" 表示任何由 空白" " 或 ":" 所组成的字串都可当成
分隔字元, 则分割后 :   
第一栏($1) = "A123", 第二栏($2) = "8", 第三栏($3) = "15"
NR 表从 AWK 开始执行该程式后所读取的资料列数.
FNR 与 NR 功用类似. 不同的是AWK每开启一个新的资料档,
FNR 便从 0 重新累计

NF表目前的资料列所被切分的栏位数.
AWK 每读入一笔资料后, 于程式中可以 NF 来得知该笔资料包含
的栏位个数.在下一笔资料被读入之前, NF 并不会改变. 但使用者
若自行使用$0来记录资料  
例如 : 使用 getline, 此时 NF 将代表新的 $0 上所记载之资料的
栏位个数.
OFS输出时的栏位分隔字元. 预设值 " "(一个空白), 详见下面说明.
ORS输出时资料列的分隔字元. 预设值 "\n"(跳行), 见下面说明.
OFMT数值资料的输出格式. 预设值 "%.6g"(若须要时最多印出6位小数)
当使用 print 指令一次印出多项资料时,
例如 : print $1, $2
印出资料时, AWK会自动在 $1 与 $2 之间补上一个 OFS 之值
(预设值为 一个空白)
每次使用 print 输出(印)资料后, AWK会自动补上 ORS 之值.
(预设值为 跳行)
使用 print 输出(印)数值资料时, AWK将采用 OFMT 之值为
输出格式.
例如 : print 2/3
     AWK 将会印出 0.666667  
程式中可藉由改变这些变数之值, 来改变指令 print 的输出格式.  

RS( Record Separator) : AWK从资料档上读取资料时,
将依 RS 之定义把资料切割成许多Records,而AWK一次仅读入一个
Record,以进行处理.
RS 的预设值是 "\n". 所以一般 AWK一次仅读入一行资料.
有时一个Record含括了几列资料(Multi-line Record). 这情况下不能
再以"\n"
来分隔并邻的Records, 可改用 空白行 来分隔.
在AWK程式中,令 RS = "" 表示以 空白行 来分隔并邻的Records.
*
RSTART与使用字串函数 match( )有关之变数,详见下面说明.
RLENGTH与使用字串函数match( )有关之变数.
当使用者使用 match(...) 函数后, AWK会将 match(...) 执行的结果以
RSTART,RLENGTH 记录之.
请参考 附录  C AWK的内建函数 match().
SUBSEP(Subscript Separator) 阵列中注标的分隔字元,
预设值为"\034"实际上, AWK中的 阵列 只接受 字串 当它的注标,
如 : Arr["John"].
但使用者在 AWK 中仍可使用 数字 当阵列的注标, 甚至可使用多维的
阵列(Multi-dimenisional Array)
如 : Arr[2,79]
事实上, AWK在接受 Arr[2,79] 之前, 就已先把其注标转换成字串
"2\03479", 之后便以 Arr["2\03479"] 代替 Arr[2,79].
可参考下例 :
awk 'BEGIN { Arr[2,79] = 78
                    print  Arr[2,79]
                    print  Arr[ 2 , 79 ]
                    print  Arr["2\03479"]
                    idx = 2 SUBSEP 79
                    print Arr[idx]
                 }
             ' $*
         执行结果印出:
          78
          78
          78
          78



--------------------------------------------------------------------------------

Regular Expression 简介


为什么要使用 Regular Expression
UNIX 中提供了许多 指令 或 tools, 它们具有在档案中 寻找(Search)
字串或置换(Replace)字串 的功能. 像 grep, vi , sed, awk,...
不论是找寻字串或置换字串, 都得先 ``告诉这些指令所要找寻
(被置换)的字串为何''.若未能预先明确知道所要找寻(被置换)的字串
为何, 只知该字串存在的范围或特征时,
例如 :
        (一)找寻 ``T0.c'', ``T1.c'', ``T2.c''.... ``T9.c'' 当中的任一字串.
        (二)找寻至少存在一个 ``A''的任意字串.
这情况下, 如何告知执行找寻字串的指令所要找寻的字串为何.
例 (一) 中, 要找寻任一在 ``T'' 与 ``.c'' 之间存在一个阿拉伯数字
的字串;当然您可以列举的方式, 一一把所要找寻的字串告诉执行
命令的指令.但例 (二) 中合于该条件的字串有无限种可能, 势必无法
一一列举.
此时,便需要另一种字串表示的方法(协定).
  什么是 Regular Expression
Regular Expression(以下简称 (Regexp)是一种字串表达的方式.
可用以指称具有某特征的所有字串.
注 : 为区别于一般字串, 本附录中代表 Regexp 的字串之前皆加
``Regexp''.注 : AWK 程式中常以/..../括住 Regexp; 以区别于一般字串.

组成 Regular Expression 的元素
普通字元 除了.  * [ ] + ? ( ) \  \^ $ 外之所有字元.
由普通字元所组成的Regexp其意义与原字串字面意义相同.
例如 : Regexp ``the'' 与一般字串的 ``the'' 代表相同的意义.

. Metacharacter : 用以代表任意一字元.
须留心 UNIX Shell 中使用 ``*''表示 Wildcard, 可用以代表任意长度
的字串.而 Regexp 中使用 ``.'' 来代表一个任意字元.(注意: 并非
任意长度的字串)Regexp 中 ``*'' 另有其它涵意, 并不代表任意
长度的字串.
^ 表示该字串必须出现于行首.   
$ 表示该字串必须出现于行末.  
例如 :
     Regexp /^The/ 用以表示所有出现于行首的字串 "The".
     Regexp /The$/ 用以表示所有出现于行末字串 "The".
\ 将特殊字元还原成字面意义的字元(Escape character)
Regexp 中特殊字元将被解释成特定的意义. 若要表示特殊字元的字面
(literal meaning)意义时,在特殊字元之前加上"\"即可.
       例如 :
          使用Regexp来表示字串 ``a.out''时, 不可写成 /a.out/.
          因为 ``.''是特殊字元, 表任一字元. 可合乎 Regexp / a.out/
          的字串将不只 ``a.out'' 一个; 字串 ``a2out'', ``a3out'',      
          ``aaout'' ...都合于 Regexp /a.out/.
          正确的用法为 :  / a\.out/
[...]字元集合, 用以表示两中括号间所有的字元当中的任一个.
例如 : Regexp /[Tt]/ 可用以表示字元 ``T'' 或 ``t''.
      故 Regexp /[Tt]he/ 表示 字串 ``The'' 或 ``the''.
      字元集合 [... ] 内不可随意留空白.
例如 : Regexp /[ Tt ]/ 其中括号内有空白字元, 除表示
       ``T'', ``t'' 中任一个字元, 也可代表一个 `` ''(空白字元)
- 字元集合中可使用 ``-'' 来指定字元的区间, 其用法如下 :
Regexp / [0-9]/ 等于 / [0123456789]/ 用以表示任意一个阿拉伯数字.
同理 Regexp /[A-Z]/ 用以表示任意一个大写英文字母.
但应留心 :

Regexp /[0-9a-z]/ 并不等于 /[0-9][a-z]/; 前者表示一个字元,
      后者表示二个字元.
Regexp /[-9]/ 或 /[9-]/ 只代表字元 ``9''或 ``-''.

[^...]使用[^..] 产生字元集合的补集(complement set).
其用法如下 :
例如 : 要指定 ``T'' 或 ``t'' 之外的任一个字元, 可用 /[^Tt]/ 表之.
同理 Regexp /[^a-zA-Z]/ 表示英文字母之外的任一个字元.
须留心 ``^'' 之位置 : ``^''必须紧接于``["之后, 才代表字元集合的补集
例如 :Regexp /[0-9\^]/ 只是用以表示一个阿拉伯数字或字元"^".
*  形容字元重复次数的特殊字元.
``*'' 形容它前方之字元可出现 1 次或多次, 或不出现.
例如 :
Regexp /T[0-9]*\.c 中 * 形容其前 [0-9] (一个阿拉伯数字)出现的次数
可为 0次或 多次.故Regexp /T[0-9]*\.c/ 可用以表示
``T.c'', ``T0.c'', ``T1.c''...``T9.c''
+形容其前的字元出现一次或一次以上.
例如 : Regexp /[0-9]+/ 用以表示一位或一位以上的数字.
?  形容其前的字元可出现一次或不出现.
例如 : Regexp /[+-]?[0-9]+/ 表示数字(一位以上)之前可出现正负号
或不出现正负号.
(...)用以括住一群字元,且将之视成一个group(见下面说明)
        例如 :
       Regexp /12+/   表示字串 ``12'', ``122'', ``1222'', ``12222'',...
       Regexp /(12)+/ 表示字串 ``12'', ``1212'', ``121212'', ``12121212''....
上式中 12 以( )括住, 故 ``+''所形容的是 12, 重复出现的也是 12.
| 表示逻辑上的"或"(or)
例如 :
Regexp / Oranges? | apples?  | water/ 可用以表示 :
   字串 ``Orange'', ``oranges'' 或 ``apple'', ``apples''  或 ``water''

match是什么 ?  
讨论 Regexp 时, 经常遇到 ``某字串合于( match )某 Regexp''的字眼.
其意思为 :  ``这个 Regexp 可被解释成该字串''.
[ 例如] :
字串 "the" 合于(match) Regexp /[Tt]he/.
因为 Regexp /[Tt]he/ 可解释成字串 "the" 或 "The", 故字串 "the"
或 "The"都合于(match) Regexp /[Th]he/.

AWK 中提供二个关系运算元(Relational Operator,见注一) ~ !~,
它们也称之为 match, not match.但函义与一般常称的 match 略有不同.
其定义如下 :
A  表一字串, B 表一 Regular Expression
只要 A 字串中存在有子字串可 match( 一般定义的 match) Regexp  B ,
则 A ~B 就算成立, 其值为 true, 反之则为 false.
! ~ 的定义与~恰好相反.
{itemize}
例 如 :
          "another" 中含有子字串 "the" 可 match Regexp /[Tt]he/ , 所以
          "another" ~/[Tt]he/  之值为 true.
*
[注 一] : 有些论着不把这两个运算元( ~, !~)与 Relational Operators
归为一类.
               
应用 Regular Expression 解题的简例
下面列出一些应用 Regular Expression 的简例, 部分范例中会更动
$0 之值, 若您使用的 AWK
不容许使用者更动 $0时, 请改用 gawk.   

将档案中所有的字串 "Regular Expression" 或 "Regular expression"
换成 "Regexp"
awk '
   {   gsub( /Regular[ \t]+[Ee]xpression/, "Regexp")
       print
   }
  ' $*
去除档案中的空白行(或仅含空白字元或tab)
awk '
      $0 !~ /^[ \t]*$/  {  print   }
' $*
在档案中俱有 ddd-dddd(电话号码型态, d 表digital)的字串前
加上"TEL : "

awk '
    {   gsub( /[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/, "TEL : &" )
        print
    }
  ' $*

从档案的 Fullname 中分离出 路径 与 档名

awk '
BEGIN{
        Fullname = "/usr/local/bin/xdvi"
        match( Fullname, /.*\//)
        path = substr(Fullname, 1, RLENGTH-1)
        name = substr(Fullname, RLENGTH+1)
        print "path :", path,"  name :",name
      }
     ' $*
结果印出
    path : /usr/local/bin   name : xdvi

将某一数值改以现金表示法表之(整数部分每三位加一撇,
且含二位小数)

awk '
BEGIN {
         Number = 123456789
         Number = sprintf("$%.2f",Number)
         while( match(Number,/[0-9][0-9][0-9][0-9]/ ) )
                sub(/[0-9][0-9][0-9][.,]/, ",&", Number)
         print Number
   
       }
   ' $*
结果印出
    $123,456,789.00

把档案中所有具 ``program数字.f''形态的字串改为
``[Ref : program数字.c]''

awk '
    {   while( match( $0, /program[0-9]+\.f/ )  ){
           Replace = "[Ref : " substr( $0, RSTART, RLENGTH-2) ".c]"
           sub( /program[0-9]+\.f/, Replace)
         }
        print
     }
  ' $*     

        


--------------------------------------------------------------------------------

[[i] Last edited by 无奈何 on 2006-10-27 at 02:28 AM [/i]]
作者: 无奈何     时间: 2006-10-27 02:19    标题: AWK

AWK
中科永联高级技术培训中心(www.itisedu.com

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

      你可能对UNIX比较熟悉,但你可能对awk很陌生,这一点也不奇怪,的确,与其优秀的功能相比,awk还远没达到它应有的知名度。awk是什么?与其它大多数UNIX命令不同的是,从名字上看,我们不可能知道awk的功能:它既不是具有独立意义的英文单词,也不是几个相关单词的缩写。事实上,awk是三个人名的缩写,他们是:Aho、(Peter)Weinberg和 (BrAIn)Kernighan。正是这三个人创造了awk---一个优秀的样式扫描与处理工具。

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

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

    awk '{pattern + action}' {filenAMES}

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

    GAwk 是 AWK 的 GNU 版本。

一、AWK的功能是什么?

      与sed和 grep很相似,awk是一种样式扫描与处理工具。但其功能却大大强于sed和grep。awk提供了极其强大的功能:它几乎可以完成grep和sed所能完成的全部工作,同时,它还可以可以进行样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。它具备了一个完整的语言所应具有的几乎所有精美特性。实际上,awk的确拥有自己的语言:awk程序设计语言,awk的三位创建者已将它正式定义为:样式扫描和处理语言。

二、为什么使用awk?

      即使如此,你也许仍然会问,我为什么要使用awk?

      使用awk的第一个理由是基于文本的样式扫描和处理是我们经常做的工作,awk所做的工作有些象数据库,但与数据库不同的是,它处理的是文本文件,这些文件没有专门的存储格式,普通的人们就能编辑、阅读、理解和处理它们。而数据库文件往往具有特殊的存储格式,这使得它们必须用数据库处理程序来处理它们。既然这种类似于数据库的处理工作我们经常会遇到,我们就应当找到处理它们的简便易行的方法,UNIX有很多这方面的工具,例如sed 、grep、sort以及find等等,awk是其中十分优秀的一种。

      使用awk的第二个理由是awk是一个简单的工具,当然这是相对于其强大的功能来说的。的确,UNIX有许多优秀的工具,例如UNIX天然的开发工具C语言及其延续C++就非常的优秀。但相对于它们来说,awk完成同样的功能要方便和简捷得多。这首先是因为awk提供了适应多种需要的解决方案:从解决简单问题的awk命令行到复杂而精巧的awk程序设计语言,这样做的好处是,你可以不必用复杂的方法去解决本来很简单的问题。例如,你可以用一个命令行解决简单的问题,而C不行,即使一个再简单的程序,C语言也必须经过编写、编译的全过程。其次,awk本身是解释执行的,这就使得awk程序不必经过编译的过程,同时,这也使得它与shell sCRipt程序能够很好的契合。最后,awk本身较C语言简单,虽然awk吸收了C语言很多优秀的成分,熟悉C语言会对学习awk有很大的帮助,但 awk本身不须要会使用C语言——一种功能强大但需要大量时间学习才能掌握其技巧的开发工具。

       使用awk的第三个理由是awk是一个容易获得的工具。与C和C++语言不同,awk只有一个文件(/BIn/awk),而且几乎每个版本的UNIX都提供各自版本的awk,你完全不必费心去想如何获得awk。但C语言却不是这样,虽然C语言是UNIX天然的开发工具,但这个开发工具却是单独发行的,换言之,你必须为你的UNIX版本的C语言开发工具单独付费(当然使用D版者除外),获得并安装它,然后你才可以使用它。

      基于以上理由,再加上awk强大的功能,我们有理由说,如果你要处理与文本样式扫描相关的工作,awk应该是你的第一选择。在这里有一个可遵循的一般原则:如果你用普通的shell工具或shell script有困难的话,试试awk,如果awk仍不能解决问题,则便用C语言,如果C语言仍然失败,则移至C++。

三、awk的调用方式

      前面曾经说过,awk提供了适应多种需要的不同解决方案,它们是:

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

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

      3、利用命令解释器调用awk程序:利用UNIX支持的命令解释器功能,我们可以将一段awk程序写入文本文件,然后在它的第一行加上:
#!/bin/awk -f
并赋予这个文本文件以执行的权限。这样做之后,你就可以在命令行中用类似于下面这样的方式调用并执行这段awk程序了。

$awk脚本文本名 待处理文件

awk的语法:

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

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

参数说明:

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

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

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

'pattern {action}'

      其中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处理的工作与数据库的处理方式有相同之处,其相同处之一就是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 {printf $1 $3 $7}'

四、awk的内置函数

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

      作为内置函数的一个例子,我们将在这里介绍awk的printf函数,这个函数使得awk与c语言的输出相一致。实际上,awk中有许多引用形式都是从C语言借用过来的。如果你熟悉C语言,你也许会记得其中的printf函数,它提供的强大格式输出功能曾经带我们许多的方便。幸运的是,我们在awk中又和它重逢了。awk中printf几乎与C语言中一模一样,如果你熟悉C语言的话,你完全可以照C语言的模式使用awk中的printf。因此在这里,我们只给出一个例子,如果你不熟悉的话,请随便找一本C语言的入门书翻翻。

例:显示文件myfile中的行号和第3字段:

$awk '{printf"%03d%sn",NR,$1}' myfile

五、在命令行使用awk

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

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

$awk '/sun/{print}' mydoc

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

$awk '/sun/' mydoc

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

$awk '/[Ss]un/,/[Mm]OOn/ {print}' 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 提供两种变量,一种是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的流程控制

      流程控制语句是任何程序设计语言都不能缺少的部分。任何好的语言都有一些执行流程控制的语句。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)
语句1
else
语句2
}
语句3
else {if(表达式3)
语句4
else
语句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也不例外,但原始的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("当前页页号是:%dn",pageno);
>}

>#定义函数print_header
>function print_header(FileName,PageNum){
>printf("%s %dn",FileName,PageNum); >PageNum++;return PageNUm;
>}
>}' myfile

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

myfile 1
当前页页号是:2

十、awk高级输入输出

      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<"filename"方式从一个给定的文件中输入数据,而不是从命令行所列内容输入数据。此时,getline将完成一般字段分离(设置字段变量$0和NF)。如果文件不存在,返回-1,成功,返回1,返回0表示失败。用户可以从给定文件中读取数据到一个变量中,也可以用stDIn(标准输入设备)或一个包含这个文件名的变量代替filename。值得注意的是当使用这种方式时不修改FNR和NR。

      另一种使用getline语句的方法是从UNIX命令接受输入,例如下面的例子:

例:示例从UNIX命令接受输入

{while("who -u"|getline)
{
#process each line from the who command
}
}

当然,也可以使用如下形式:

"command" | getline variable

      3.关闭文件:

      awk中允许在程序中关闭一个输入或输出文件,方法是使用awk的close语句。

close("filename"t

      filename可以是getline打开的文件(也可以是stdin,包含文件名的变量或者getline使用的确切命令)。或一个输出文件(可以是stdout,包含文件名的变量或使用管道的确切命令)。

      4.输出到一个文件:

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

printf("hello word!n"t>"datafile"

printf("hello word!n"t>>"datafile"

      5.输出到一个命令

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

printf("hello word!n"t|"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 script读取awk的执行结果等等。

1.awk读取Shell script程序变量

在awk中我们可以通过“'$变量名'”的方式读取sell scrpit程序中的变量。

例:在下面的示例中,我们将读取sell scrpit程序中的变量Name,该变量存放的是文本myfile的撰写者,awk将打印出这个人名。

$cat writename
:
# @(#)
#
.
.
.
Name="张三" nawk 'BEGIN {name="'Name'"; printf("t%st撰写者%sn",FILENAME,name"t;}
{...}END{...}' myfile
.
.
.

2.将shell命令的执行结果送给awk处理

作为信息传送的一种方法,我们可以将一条shell命令的结果通过管道线(|)传递给awk处理:

例:示例awk处理shell命令的执行结果

$who -u | awk '{printf("%s正在执行%sn",$2,$1)}'

该命令将打印出注册终端正在执行的程序名。

3.shell script程序读awk的执行结果

为了实现shell script程序读取awk执行的结果,我们可以采取一些特殊的方法,例如我们可以用变量名=`awk语句`的形式将awk执行的结果存放入一个 shell script变量。当然也可以用管道线的方法将awk执行结果传递给shell script程序处理。

例:作为传送消息的机制之一,UNIX提供了一个向其所有用户传送消息的命令wall(意思是write to all写给所有用户),该命令允许向所有工作中的用户(终端)发送消息。为此,我们可以通过一段shell批处理程序wall.shell来模拟这一程序(事实上比较老的版本中wall就是一段shell批处理程序:

$cat wall.shell
:
# @(#) wall.shell:发送消息给每个已注册终端
#
cat >/tmp/$$
#用户录入消息文本 who -u | awk '{print $2}' | while read tty
do
cat /tmp/$$>$tty
done

在这个程序里,awk接受who -u命令的执行结果,该命令打印出所有已注册终端的信息,其中第二个字段是已注册终端的设备名,因此用awk命令析出该设备名,然后用while read tty语句循环读出这些文件名到变量(shell script变量)tty中,作为信息传送的终结地址。

4.在awk中执行shell命令行----嵌入函数system()

system()是一个不适合字符或数字类型的嵌入函数,该函数的功能是处理作为参数传递给它的字符串。system对这个参数的处理就是将其作为命令处理,也就是说将其当作命令行一样加以执行。这使得用户在自己的awk程序需要时可以灵活地执行命令或脚本。

例:下面的程序将使用system嵌入函数打印用户编制好的报表文件,这个文件存放在名为myreport.txt的文件中。为简约起见,我们只列出了其END部分:

.
.
.
END {close("myreport.txt"t;system("lp myreport.txt"t;}

在这个示例中,我们首先使用close语句关闭了文件myreport.txt文件,然后使用system嵌入函数将myreport.txt送入打印机打印。

写到这里,我不得不跟朋友们说再见了,实在地说,这些内容仍然是awk的初步知识,电脑永远是前进的科学,awk也不例外,本篇所能做的只是在你前行的漫漫长途中铺平一段小小开端,剩下的路还得靠你自己去走。老实说,如果本文真能给你前行的路上带来些许的方便,那本人就知足了!

如对本篇有任何疑问,请E-mail To:Chizlong@yeah.net或到主页http://chizling.yeah.net中留言。


附录:

1.awk的常规表达式元字符

换码序列
^ 在字符串的开头开始匹配
$ 在字符串的结尾开始匹配
. 与任何单个字符串匹配
[ABC] 与[]内的任一字符匹配
[A-Ca-c] 与A-C及a-c范围内的字符匹配(按字母表顺序)
[^ABC] 与除[]内的所有字符以外的任一字符匹配
Desk|Chair 与Desk和Chair中的任一个匹配
[ABC][DEF] 关联。与A、B、C中的任一字符匹配,且其后要跟D、E、F中的任一个字符。
* 与A、B或C中任一个出现0次或多次的字符相匹配
+ 与A、B或C中任何一个出现1次或多次的字符相匹配
? 与一个空串或A、B或C在任何一个字符相匹配
(Blue|Black)berry 合并常规表达式,与Blueberry或Blackberry相匹配

2.awk算术运算符

运算符 用途
------------------
x^y x的y次幂
x**y 同上
x%y 计算x/y的余数(求模)
x+y x加y
x-y x减y
x*y x乘y
x/y x除y
-y 负y(y的开关符号);也称一目减
++y y加1后使用y(前置加)
y++ 使用y值后加1(后缀加)
--y y减1后使用y(前置减)
y-- 使用后y减1(后缀减)
x=y 将y的值赋给x
x+=y 将x+y的值赋给x
x-=y 将x-y的值赋给x
x*=y 将x*y的值赋给x
x/=y 将x/y的值赋给x x%=y 将x%y的值赋给x
x^=y 将x^y的值赋给x
x**=y 将x**y的值赋给x

3.awk允许的测试:

操作符 含义

x==y x等于y
x!=y x不等于y
x>y x大于y
x>=y x大于或等于y
xx<=y x小于或等于y?
x~re x匹配正则表达式re?
x!~re x不匹配正则表达式re?

4.awk的操作符(按优先级升序排列)

= 、+=、 -=、 *= 、/= 、 %=
||
&&
> >= < <= == != ~ !~
xy (字符串连结,'x''y'变成"xy")
+ -
* / %
++ --

5.awk内置变量(预定义变量)

说明:表中v项表示第一个支持变量的工具(下同):A=awk,N=nawk,P=POSIX awk,G=gawk

V 变量 含义 缺省值
--------------------------------------------------------
N ARGC 命令行参数个数
G ARGIND 当前被处理文件的ARGV标志符
N ARGV 命令行参数数组
G CONVFMT 数字转换格式 %.6g
P ENVIRON UNIX环境变量
N ERRNO UNIX系统错误消息
G FIELDWIDTHS 输入字段宽度的空白分隔字符串
A FILENAME 当前输入文件的名字
P FNR 当前记录数
A FS 输入字段分隔符 空格
G IGNORECASE 控制大小写敏感0(大小写敏感)
A NF 当前记录中的字段个数
A NR 已经读出的记录数
A OFMT 数字的输出格式 %.6g
A OFS 输出字段分隔符 空格
A ORS 输出的记录分隔符 新行
A RS 输入的记录他隔符 新行
N RSTART 被匹配函数匹配的字符串首
N RLENGTH 被匹配函数匹配的字符串长度
N SUBSEP 下标分隔符 "34"

6.awk的内置函数

V 函数 用途或返回值
------------------------------------------------
N gsub(reg,string,target) 每次常规表达式reg匹配时替换target中的string
N index(search,string) 返回string中search串的位置
A length(string) 求串string中的字符个数
N match(string,reg) 返回常规表达式reg匹配的string中的位置
N printf(fORMat,variable) 格式化输出,按format提供的格式输出变量variable。
N split(string,store,delim) 根据分界符delim,分解string为store的数组元素
N sprintf(format,variable) 返回一个包含基于format的格式化数据,variables是要放到串中的数据
G strftime(format,timestamp) 返回一个基于format的日期或者时间串,timestmp是systime()函数返回的时间
N sub(reg,string,target) 第一次当常规表达式reg匹配,替换target串中的字符串
A substr(string,position,len) 返回一个以position开始len个字符的子串
P totower(string) 返回string中对应的小写字符
P toupper(string) 返回string中对应的大写字符
A atan(x,y) x的余切(弧度)
N cos(x) x的余弦(弧度)
A eXP(x) e的x幂
A int(x) x的整数部分
A log(x) x的自然对数值
N rand() 0-1之间的随机数
N sin(x) x的正弦(弧度)
A sqrt(x) x的平方根
A srand(x) 初始化随机数发生器。如果忽略x,则使用system()
G system() 返回自1970年1月1日以来经过的时间(按秒计算)

[ Last edited by 无奈何 on 2006-10-27 at 02:31 AM ]
作者: 无奈何     时间: 2006-10-27 02:20    标题: GAWK 手册

转贴注:原始链接未知
GAWK 手册

作者:[url=mailto:rezaie@softhome.net]Wilbur Lang[/url]

第一章 前言
第二章 简介
第三章 读取输入档案
第四章 印出
第五章 Patterns
第六章 算式(Expression)作为Actions的叙述
第七章 Actions里面的控制叙述
第八章 内建函式(Built-in Functions)
第九章 使用者定义的函式
第十章 □例
第十一章 结论

第一章 前言

awk 是一个程式语言,对於资料的处理具有很强的功能。对於文
字档里的资料做修改、比对、抽取等的处理,awk 能够以很短的程式
轻易地完成。如果使用 C 或 Pascal 等语言写程式完成上述的动作,
会不方便且很花费时间,所写的程式也会很大。

awk 能够依照使用者的定义格式来分解输入资料,也可依照使用
者定义的格式来印出资料。

awk 名称的由来是由它的原始设计者的姓氏之第一个字母而命名
:Alfred V. Aho, Peter J. Weinberger, Brian W. Kernighan。
awk最初在1977年完成。一个新版本的awk在1985年被发表,它的功能
比旧版本增强不少。

gawk 是GNU所做的 awk,gawk 最初在1986年完成,之後不断地
被改进、更新。gawk 包含 awk 的所有功能。

往後的 gawk 将以下面的2个输入档案来做例子说明。

档案'BBS-list':
aardvark 555-5553 1200/300 B
alpo-net 555-3412 2400/1200/300 A
barfly 555-7685 1200/300 A
bites 555-1675 2400/1200/300 A
camelot 555-0542 300 C
core 555-2912 1200/300 C
fooey 555-1234 2400/1200/300 B
foot 555-6699 1200/300 B
macfoo 555-6480 1200/300 A
sdace 555-3430 2400/1200/300 A
sabafoo 555-2127 1200/300 C


档案'shipped':
Jan 13 25 15 115
Feb 15 32 24 226
Mar 15 24 34 228
Apr 31 52 63 420
May 16 34 29 208
Jun 31 42 75 492
Jul 24 34 67 436
Aug 15 34 47 316
Sep 13 55 37 277
Oct 29 54 68 525
Nov 20 87 82 577
Dec 17 35 61 401

Jan 21 36 64 620
Feb 26 58 80 652
Mar 24 75 70 495
Apr 21 70 74 514



第二章 简介

gawk 的主要功能是针对档案的每一行(line)搜寻指定的 patterns
。当一行里有符合指定的 patterns,gawk 就会在此一行执行被指定
的 actions。 gawk 依此方式处理输入档案的每一行直到输入档案结
束。

gawk 程式是由很多的 pattern 与 action 所组成,action 写在
大括号 { } 里面,一个pattern後面就跟著一个action。整个 gawk 程
式会像下面的样子:

pattern {action}
pattern {action}

在 gawk 程式里面的规则,pattern 或 action 能够被省略,但
是两个不能同时被省略。如果 pattern 被省略,对於输入档里面的
每一行,action 都会被执行。如果 action 被省略,内定的 action
则会印出所有符合 pattern 的输入行。



2.1 如何执行gawk程式

基本上,有2个方法可以执行gawk程式。

□如果 gawk 程式很短,则 gawk 可以直接写在 command line,如下所示:

gawk 'program' input-file1 input-file2 ...

其中 program 包括一些 pattern 和 action。

□如果 gawk 程式较长,较为方便的做法是将 gawk 程式存在一个档案,
即 patterns 与 actions 写在档名为 program-file 的档案里面,执行
gawk 的格式如下所示:

gawk -f program-file input-file1 input-file2 ...

gawk 程式的档案不止一个时,执行gawk 的格式如下所示:

gawk -f program-file1 -f program-file2 ... input-file1
input-file2 ...



2.2 一个简单的例子

现在我们举一个简单的例子,因为 gawk 程式很短,所以将 gawk 程
式直接写在 command line。

gawk '/foo/ {print $0}' BBS-list

实际的 gawk 程式为 /foo/ {print $0}。/foo/ 为 pattern,意思为搜
寻输入档里的每一行是否含有子字串 'foo',如果含有 'foo' 则执行 action。
action 为 print $0,是将现在这一行的内容印出。BBS-list 是输入的档案。

执行完上述指令後,会印出下面的结果:
fooey 555-1234 2400/1200/300 B
foot 555-6699 1200/300 B
macfoo 555-6480 1200/300 A
sabafoo 555-2127 1200/300 C



2.3 一个较复杂的例子

gawk '$1 == "Feb" {sum=$2+$3} END {print sum}' shipped

现在这个例子会将输入档 'shipped' 的第一个栏位与 "Feb" 做比较
,如果相等,则其对应的第2栏位与第3栏位的值会被加到变数 sum。
对於输入档的每一行重复上述的动作,直到输入档的每一行都被处理
过为止。最後将 sum 的值印出。END {print sum} 的意思为在所有的输
入读完之後,执行一次 print sum 的动作,也就是把 sum 的值印出。

下面是执行的结果:
84


第三章 读取输入档案

gawk的输入可以从标准输入或指定的档案里读取。输入的读取单
位被称为”记录”(records),gawk 在做处理时,是一个记录一个记 (p9 of 46)
录地处理。每个记录的内定值是一行(line),一个记录又被分为多个
栏位(fields)。



3.1 如何将输入分解成记录(records)

gawk 语言会把输入分解成记录(record)。记录与记录之间是以
record separator 隔开,record separator 的内定值是表示新一行的
字元(newline character),因此内定的 record separator 使得文字
的每一行是一个记录。

record separator 随著内建变数 RS 的改变而改变。RS 是一个字串,
它的内定值是"\n"。仅有 RS 的第一个字元是有效的,它被当作 record
separator,而 RS 的其它字元会被忽略。

内建变数 FNR 会储存目前的输入档案已颈欢寥〉募锹贾鍪D
建变数 NR 会储存目前为止所有的输入档案已颈欢寥〉募锹贾鍪

3.2 栏位(field)

gawk 会自动将每个记录分解成多个栏位 (field)。类似於字在一
行里面,gawk 的内定动作会认为栏位之间是以 whitespace 分开。在
gawk 里,whitespace 的意思是一个或多个空白或 tabs。

在 gawk 程式里面,以'$1'表示第一个栏位,'$2'表示第二个栏位
,依此类推。举个例子,假设输入的一行如下所示:

This seems like a pretty nice example.

第一个栏位或 $1 是'This',第二个栏位或 $2 是 'seems',依此类推。
有个地方值得特别注意,第七个栏位或 $7 是'example.'而非'example'。

不论有多少栏位,$NF 可用来表示一个记录的最後一个栏位。以
上面的例子为例,$NF 与 $7 相同,也就是'example.'。

NF 是一个内建变数,它的值表示目前这个记录之栏位的个数。 $0,看起来好像是第零个栏位,它是一个特例,它表示整个记录。

下面是一个较复杂的例子:

gawk '$1~/foo/ {print $0}' BBS-list

结果如下:
fooey 555-1234 2400/1200/300 B
foot 555-6699 1200/300 B
macfoo 555-6480 1200/300 A
sabafoo 555-2127 1200/300 C

这个例子是把输入档'BBS-list'的每个记录的第一个栏位作检查,如
果它含有子字串'foo',则这一个记录会被印出。



3.3 如何将记录分解成栏位

gawk 根据 field separator 将一个记录分解成栏位。field sepa- rator 以内建变数 FS 表示。

举个例子,假如 field separator 是'oo',则下面的行:

moo goo gai pan

会被分成三个栏位:'m'、' g'、' gai pan'。

在 gawk 程式里,可以使用'='来改变 FS 的值。例如:

gawk 'BEGIN {FS=","}; {print $2}'

输入行如下:

John Q. Smith, 29 Oak St., Walamazoo, MI 42139

执行gawk的结果将印出字串 ' 29 Oak St.'。BEGIN 後面的 action 会在
第一个记录被读取之前执行一次。
第四章 印出

在gawk程式里,actions 最常做的事就是印出(printing)。简单
的印出,使用 printe叙述。复杂格式的印出,使用 printf 叙述。



4.1 print叙述

print 叙述用在简单、标准的输出格式。叙述的格式如下所示:

print item1, item2, ...

输出时,各个 item 之间会以一个空白分开,最後会换行(newline)。

如果 'print'叙述之後没有跟著任何东西,它与'print $0'的效
果一样,它会印出现在的记录(record)。要印出空白行可使用'print
""'。 印出一段固定的文字,可用双引号将文字的两边括起来,例如
'print "Hello there"'。
这里是一个例子,它会把每个输入记录的前二个栏位印出:

gawk '{print $1,$2}' shipped

结果如下所示:
Jan 13
Feb 15
Mar 15
Apr 31
May 16
Jun 31
Jul 24
Aug 15
Sep 13
Oct 29
Nov 20
Dec 17

Feb 26
Mar 24
Apr 21



4.2 Output Separators

前面我们已提过如果 print 叙述包含有多个 item,item 之间
用逗点分开,则印出时各个item会被一个空白隔开。你能够使用任何
的字串作为 output field separator,可以居赡诮ū涫?OFS 的设
定来更改 output field separator。OFS 的初始值为" ",即一格的
空白。

整个 print 叙述的输出被称为 output record。print 叙述输
出 output record 之後,会接著输出一个字串,此字串称为 output
record separator。内建变数 ORS 用来指明此字串。ORS 的初始值
为 "\n",也就是换行。

下面这个例子会印出每个记录的第一个栏位和第二个栏位,此二
(p16 of 46)
个栏位之间以分号';'分开,每行输出之後会加入一个空白行。

gawk 'BEGIN {OFS=";"; ORS="\n\n"} {print $1, $2}' BBS-list

结果如下所示:
aardvark;555-5553

alpo-net;555-3412

barfly;555-7685

bites;555-1675

camelot;555-0542

core;555-2912

fooey;555-1234

foot;555-6699

macfoo;555-6480

sdace;555-3430

sabafoo;555-2127




4.3 printf叙述

printf 叙述会使得输出格式较容易精确地控制。printf 叙述可以
指定每个 item 印出的宽度,也可以指定数字的各种型式。

printf 叙述的格式如下:

printf format, item1, item2, ...

print 与 printf 的差别是在於 format, printf 的引数比 print
(p18 of
多了字串 format。format 的型式与 ANSI C 的 printf 之格式相同。

printf 并不会做自动换行的动作。内建变数 OFS 与 ORS 对 printf 叙
述没有任何影响。

格式的指定以字元'%'开始,後面接著格式控制字母。

格式控制字母如下所示:

'c' 将数字以 ASCII 字元印出。
例如'printf "%C",65'会印出字元'A'。

'd' 印出十进位的整数。

'i' 印出十进位的整数。

'e' 将数字以科学符号的形式印出。
例如

print "$4.3e",1950
(p19 of

结果会印出'1.950e+03'。

'f' 将数字以浮点的形式印出。

'g' 将数字以科学符号的形式或浮点的形式印出。数字的绝对值如果
大於等於0.0001则以浮点的形式印出,否则以科学符号的形式印
出。

'o' 印出无号的八进位整数。

's' 印出一个字串。

'x' 印出无号的十六进位整数。10至15以'a'至'f'表示。

'X' 印出无号的十六进位整数。10至15以'A'至'F"表示。

'%' 它并不是真正的格式控制字母,'%%"将印出"%'。

在 % 与格式控制字母之间可加入 modifier,modifier 是用来进一
步控制输出的格式。可能的 modifier 如下所示:

'-' 使用在 width 之前,指明是向左靠齐。如果'-'没有出现,则会在
被指定的宽度向右靠齐。例如:

printf "%-4S", "foo"

会印出'foo '。

'width' 这一个数字指示相对应的栏位印出时的宽度。例如:

printf "%4s","foo"

会印出' foo'。

width 的值是一个最小宽度而非最大宽度。如果一个 item 的
值需要的宽度比 width 大,则不受 width 的影响。例如

printf "%4s","foobar"
将印出'foobar'。

'.prec' 此数字指定印出时的精确度。它指定小数点右边的位数。如
果是要印出一个字串,它指定此字串最多会被印出多少个字
元。



第五章 patterns

在 gawk 程式里面,当 pattern 符合现在的输入记录(record),其
相对应的 action 才会被执行。



5.1 Pattern的种类

这里对 gawk 的各种 pattern 型式作一整理:

/regular expression/
(p22 of
一个 regular expression 当作一个 pattern。每当输入记录 (
record)含有 regular expression 就视为符合。

expression
一个单一的 expression。当一个值不为 0 或一个字串不是空的,
则可视为符合。

pat1,pat2
一对的 patterns 以逗号分开,指定记录的□围。

BEGIN
END
这是特别的 pattern, gawk 在开始执行或要结束时会分别执行相
对应於BEGIN或END的 action。

null
这是一个空的pattern,对於每个输入记录皆视为符合pattern。

(p23 of
5.2 Regular Expressions当作Patterns

一个 regular expression 可简写为 regexp,是一种描述字串的方
法。一个 regular expression 以斜线('/')包围当作 gawk 的 pattern。

如果输入记录含有 regexp 就视为符合。例如:pattern 为 /foo/,
对於任何输入记录含有'foo'则视为符合。

下面的例子会将含有'foo'的输入记录之第2个栏位印出。

gawk '/foo/ {print $2}' BBS-list

结果如下:
555-1234
555-6699
555-6480
555-2127

regexp 也能使用在比较的算式。

(p24 of
exp ~ /regexp/
如果 exp 符合 regexp,则结果为真(true)。

exp !~ /regexp/
如果 exp 不符合 regexp,则结果为真。



5.3 比较的算式当作Patterns

比较的 pattern 用来测试两个数字或字串的关系诸如大於、等於
、小於。下面列出一些比较的pattern:

x<y 如果 x 小於 y,则结果为真。
x<=y 如果 x 小於、等於 y,则结果为真。
x>y 如果 x 大於 y,则结果为真。
x>=y 如果 x 大於、等於 y,则结果为真。
x==y 如果 x 等於 y,则结果为真。
x!=y 如果 x 不等於 y,则结果为真。
x~y 如果 x 符合 regular expression y,则结果为真。
(p25 of
x!~y 如果 x 不符合 regular expression y,则结果为真。

上面所提到的 x 与 y,如果二者皆是数字则视为数字之间的比较,
否则它们会被转换成字串且以字串的形式做比较。两个字串的比较,
会先比较第一个字元,然後比较第二个字元,依此类推,直到有不同
的地方出现为止。如果两个字串在较短的一个结束之前是相等,则视
为长的字串比短的字串大。例如 "10" 比 "9" 小,"abc" 比 "abcd" 小。



5.4 使用布林运算的Patterns

一个布林(boolean) pattern 是使用布林运算"或"('||'),"及"
('&&'),"反"('!')来组合其它的pattern。
例如:

gawk '/2400/ && /foo/' BBS-list
gawk '/2400/ || /foo/' BBS-list
gawk '! /foo/' BBS-list


第六章 算式(Expression)作为Actions的叙述

算式(Expression) 是gawk程式里面action的基本构成者。



6.1 算术运算

gawk 里的算术运算如下所示:

x+y 加
x-y 减
-x 负
+x 正。实际上没有任何影响。
x*y 乘
x/y 除
x%y 求馀数。例如 5%3=2。
x^y
x**y x 的 y 次方。例如2^3=8。



6.2 比较算式与布林算式

比较算式 (comparison expression) 用来比较字串或数字的关系
,运算符号与 C 语言相同。表列如下:

x<y
x<=y
x>y
x>=y
x==y
x!=y
x~y
x!~y

比较的结果为真(true)则其值是 1。否则其值是 0。
布林算式(boolean expression)有下面三种:

boolean1 && boolean2
boolean1 || boolean2
! boolean



6.3 条件算式(Conditional Expressions)

一个条件式算式是一种特别的算式,它含有3个运算元。
条件式算式与C语言的相同:

selector ? if-true-exp : if-false-exp

它有3个子算式。第一个子算式selector 首先会被计算。如果是真,
则if-true-exp会被计算且它的值变成整个算式的值。否则if-false-
exp 会被计算且它的值变成整个算式的值。

例如下面的例子会产生x的绝对值:
x>0 ? x : -x



第七章 Actions里面的控制叙述

在 gawk 程式里面,控制叙述诸如 if、while 等控制程式执行的流
程。在 gawk 里的控制叙述与 C 的类似。

很多的控制叙述会包括其它的叙述,被包括的叙述称为 body。假
如 body 里面包括一个以上的叙述,必须以大括弧 { } 将这些叙述括起
来,而各个叙述之间需以换行(newline)或分号隔开。



7.1 if 叙述

if (condition) then-body [else else-body]
(p30 of
如果 condition 为真(true),则执行 then-body,否则执行 else-body。

举一个例子如下:

if (x % 2 == 0)
print "x is even"
else
print "x is odd"



7.2 while 叙述

while (condition)
body

while 叙述做的第一件事就是测试 condition。假如 condition 为真则
执行 body 的叙述。body 的叙述执行完後,会再测试 condition,假如
condition 为真,则 body 会再度被执行。这个过程会一直被重复直到
condition 不再是真。如果 condition 第一次测试就是伪(false),则
body 从没有被执行。

下面的例子会印出每个输入记录(record)的前三个栏位。

gawk '{ i=1
while (i <= 3) {
print $i
i++
}
}'



7.3 do-while 叙述

do
body
while (condition)

这个 do loop 执行 body 一次,然後只要 condition 是真则会重复执行 body。
(p32 of
即使开始时 condition 是伪,body 也会被执行一次。

下面的例子会印出每个输入记录十次。

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



7.4 for 叙述

for (initialization; condition; increment)
body

此叙述开始时会执行initialization,然後只要 condition是真,它
会重复执行body与做increment 。

下面的例子会印出每个输入记录的前三个栏位。

gawk '{ for (i=1; i<=3; i++)
print $i
}'



7.5 break 叙述

break 叙述会跳出包含它的 for、while、do-while 回圈的最内层。

下面的例子会找出任何整数的最小除数,它也会判断是否为质数。

gawk '# find smallest divisor of num
{ num=$1
for (div=2; div*div <=num; div++)
if (num % div == 0)
break
if (num % div == 0)
printf "Smallest divisor of %d is %d\n", num, div
else
printf "%d is prime\n", num }'



7.6 continue 叙述
(p34 of 46)
continue 叙述使用於 for、while、do-while 回圈内部,它会跳
过回圈 body 的剩馀部分,使得它立刻进行下一次回圈的执行。

下面的例子会印出 0 至 20 的全部数字,但是 5 并不会被印出。

gawk 'BEGIN {
for (x=0; x<=20; x++) {
if (x==5)
continue
printf ("%d",x)
}
print ""
}'



7.7 next 叙述、next file 叙述、exit 叙述

next 叙述强迫 gawk 立刻停止处理目前的记录(record)而继续下一
个记录。

next file 叙述类似 next。然而,它强迫 gawk 立刻停止处理目前
的资料档。

exit 叙述会使得 gawk 程式停止执行而跳出。然而,如果 END 出现
,它会去执行 END 的 actions。



第八章 内建函式(Built-in Functions)

内建函式是 gawk 内建的函式,可在 gawk 程式的任何地方呼叫内建
函式。



8.1 数值方面的内建函式

int(x) 求出 x 的整数部份,朝向 0 的方向做舍去。例如:int(3.9)
是 3,int(-3.9) 是 -3。
(p36 of 46)
sqrt(x) 求出 x 正的平方根值。例 sqrt(4)=2
exp(x) 求出 x 的次方。例 exp(2) 即是求 e*e 。
log(x) 求出 x 的自然对数。
sin(x) 求出 x 的 sine 值,x 是弪度量。
cos(x) 求出 x 的 cosine 值,x 是弪度量。
atan2(y,x) 求 y/x 的 arctangent 值,所求出的值其单位是弪度量。
rand() 得出一个乱数值。此乱数值平均分布在 0 和 1 之间。这个
值不会是 0,也不会是 1。
每次执行 gawk,rand 开始产生数字从相同点或 seed。
srand(x) 设定产生乱数的开始点或 seed 为 x。如果在第二次你设
定相同的 seed 值,你将再度得到相同序列的乱数值。
如果省略引数 x,例如 srand(),则现在的日期、时间会
被当成 seed。这个方法可使得乱数值是真正不可预测的。
srand 的传回值(return value)是前次所设定的 seed 值。



8.2 字串方面的内建函式

index(in, find)
(p37 of 46)
它会在字串 in 里面,寻找字串 find 第一次出现的地方,传回值是
字串 find 出现在字串 in 里面的位置。如果在字串 in 里面找不到字
串 find,则传回值为 0。
例如:
print index("peanut","an")
会印出 3。

length(string)
求出 string 有几个字元。
例如:
length("abcde")
是 5。

match(string,regexp)
match 函式会在字串 string 里面,寻找符合 regexp 的最长、最靠
左边的子字串。传回值是 regexp 在 string 的开始位置,即 index
值。
match 函式会设定内在变数 RSTART 等於 index,它也会设定内在变
数 RLENGTH 等於符合的字元个数。如果不符合,则会设定 RSTART 为
0、RLENGTH 为 -1。
(p38 of 46)

sprintf(format,expression1,...)
举 printf 类似,但是 sprintf 并不印出,而是传回字串。
例如:
sprintf("pi = %.2f (approx.)',22/7)
传回的字串为"pi = 3.14 (approx.)"

sub(regexp, replacement,target)
在字串 target 里面,寻找符合 regexp 的最长、最靠左边的地方,
以字串 replacement 代替最左边的 regexp。
例如:
str = "water, water, everywhere"
sub(/at/, "ith",str)
结果字串str会变成
"wither, water, everywhere"

gsub(regexp, replacement, target)
gsub 与前面的 sub 类似。在字串 target 里面,寻找符合 regexp 的
所有地方,以字串 replacement 代替所有的 regexp。
例如:
(p39 of 46)
str="water, water, everywhere"
gsub(/at/, "ith",str)
结果字串str会变成
'wither, wither, everywhere"

substr(string, start, length)
传回字串 string 的子字串,这个子字串的长度为 length 个字元,
从第 start 个位置开始。
例如:
substr("washington",5,3)
传回值为"ing"
如果 length 没有出现,则传回的子字串是从第 start 个位置开始
至结束。
例如:
substr("washington",5)
传回值为"ington"

tolower(string)
将字串string的大写字母改为小写字母。
例如:
tolower("MiXeD cAsE 123")
传回值为"mixed case 123"

toupper(string)
将字串string的小写字母改为大写字母。
例如:
toupper("MiXeD cAsE 123")
传回值为"MIXED CASE 123"



8.3 输入输出的内建函式

close(filename)
将输入或输出的档案 filename 关闭。

system(command)
此函式允许使用者执行作业系统的指令,执行完毕後将回到 gawk
程式。
例如:
BEGIN {system("ls")}



第九章 使用者定义的函式(User-defined Functions)

复杂的 gawk 程式常常可以使用自己定义的函式来简化。呼叫使用
者定义的函式与呼叫内建函式的方法一样。



9.1 函式定义的格式

函式的定义可以放在 gawk 程式的任何地方。

一个使用者定义的函式其格式如下:

function name (parameter-list) {
body-of-function
}

name 是所定义的函式之名称。一个正确的函式名称可包括一序列的字
母、数字、下标线 (underscores),但是不可用数字做开头。

parameter-list 是列出函式的全部引数(argument),各个引数之
间以逗点隔开。

body-of-function 包含 gawk 的叙述 (statement)。它是函式定义
里最重要的部份,它决定函式实际要做何种事。



9.2 函式定义的例子

下面这个例子,会将每个记录的第一个栏位之值的平方与第二个
栏位之值的平方加起来。

{print "sum =",SquareSum($1,$2)}
function SquareSum(x,y) {
sum=x*x+y*y
return sum
}



第十章 □例

这里将列出 gawk 程式的一些例子。

gawk '{if (NF > max) max = NF}
END {print max}'
此程式会印出所有输入行之中,栏位的最大个数。

gawk 'length($0) > 80'
此程式会印出一行超过 80 个字元的每一行。此处只有 pattern 被
列出,action 是采用内定的 print。

gawk 'NF > 0'
对於拥有至少一个栏位的所有行,此程式皆会印出。这是一个简
单的方法,将一个档案里的所有空白行删除。

gawk '{if (NF > 0) print}'
对於拥有至少一个栏位的所有行,此程式皆会印出。这是一个简
单的方法,将一个档案里的所有空白行删除。

gawk 'BEGIN {for (i = 1; i <= 7; i++)
print int(101 * rand())}'
此程式会印出□围是 0 到 100 之间的 7 个乱数值。

ls -l files | gawk '{x += $4}; END {print "total bytes: " x}'
此程式会印出所有指定的档案之bytes数目的总和。

expand file | gawk '{if (x < length()) x = length()}
END {print "maximum line length is " x}'
此程式会将指定档案里最长一行的长度印出。expand 会将 tab 改
成 space,所以是用实际的右边界来做长度的比较。

gawk 'BEGIN {FS = ":"}
{print $1 | "sort"}' /etc/passwd
此程式会将所有使用者的login名称,依照字母的顺序印出

gawk '{nlines++}
END {print nlines}'
此程式会将一个档案的总行数印出。

gawk 'END {print NR}'
此程式也会将一个档案的总行数印出,但是计算行数的工作由gawk
来做。

gawk '{print NR,$0}'
此程式印出档案的内容时,会在每行的最前面印出行号,它的功
能与 'cat -n' 类似。



第十一章 结论

gawk 对於资料的处理具有很强的功能。它能够以很短的程式完成
想要做的事,甚至一或二行的程式就能完成指定的工作。同样的一件
工作,以 gawk 程式来写会比用其它程式语言来写短很多。
gawk 是 GNU 所做的 awk,它是公众软体(Public Domain) 可免费使
用。

[ Last edited by 无奈何 on 2006-10-27 at 02:40 AM ]
作者: 无奈何     时间: 2006-10-27 02:20    标题: awk用法小结(作者总结)

转贴注:原始链接http://bbs.chinaunix.net/viewthr ... &extra=page%3D1
awk 用法:awk ' pattern {action} '

变量名                含义
ARGC                命令行变元个数
ARGV                命令行变元数组
FILENAME        当前输入文件名
FNR                当前文件中的记录号
FS                输入域分隔符,默认为一个空格
RS                输入记录分隔符
NF                当前记录里域个数
NR                到目前为止记录数
OFS                输出域分隔符
ORS                输出记录分隔符

1、awk '/101/'               file 显示文件file中包含101的匹配行。
   awk '/101/,/105/'         file
   awk '$1 == 5'             file
   awk '$1 == "CT"'          file 注意必须带双引号
   awk '$1 * $2 >100 '       file
   awk '$2 >5 && $2<=15'     file
2、awk '{print NR,NF,$1,$NF,}' file 显示文件file的当前记录号、域数和每一行的第一个和最后一个域。
   awk '/101/ {print $1,$2 + 10}' file 显示文件file的匹配行的第一、二个域加10。
   awk '/101/ {print $1$2}'  file
   awk '/101/ {print $1 $2}' file 显示文件file的匹配行的第一、二个域,但显示时域中间没有分隔符。
3、df | awk '$4>1000000 '         通过管道符获得输入,如:显示第4个域满足条件的行。
4、awk -F "|" '{print $1}'   file 按照新的分隔符“|”进行操作。
   awk  'BEGIN { FS="[: \t|]" }
   {print $1,$2,$3}'              file 通过设置输入分隔符(FS="[: \t|]")修改输入分隔符。

   Sep="|"
   awk -F $Sep '{print $1}'  file 按照环境变量Sep的值做为分隔符。   
   awk -F '[ :\t|]' '{print $1}' file 按照正则表达式的值做为分隔符,这里代表空格、:、TAB、|同时做为分隔符。
   awk -F '[][]'    '{print $1}' file 按照正则表达式的值做为分隔符,这里代表[、]
5、awk -f awkfile              file 通过文件awkfile的内容依次进行控制。
   cat awkfile
/101/{print "\047 Hello! \047"} --遇到匹配行以后打印 ' Hello! '.\047代表单引号。
{print $1,$2}                   --因为没有模式控制,打印每一行的前两个域。
6、awk '$1 ~ /101/ {print $1}' file 显示文件中第一个域匹配101的行(记录)。
7、awk   'BEGIN { OFS="%"}
   {print $1,$2}'           file 通过设置输出分隔符(OFS="%")修改输出格式。
8、awk   'BEGIN { max=100 ;print "max=" max}             BEGIN 表示在处理任意行之前进行的操作。
   {max=($1 >max ?$1:max); print $1,"Now max is "max}' file 取得文件第一个域的最大值。
   (表达式1?表达式2:表达式3 相当于:
   if (表达式1)
       表达式2
   else
       表达式3
   awk '{print ($1>4 ? "high "$1: "low "$1)}' file
9、awk '$1 * $2 >100 {print $1}' file 显示文件中第一个域匹配101的行(记录)。
10、awk '{$1 == 'Chi' {$3 = 'China'; print}' file 找到匹配行后先将第3个域替换后再显示该行(记录)。
    awk '{$7 %= 3; print $7}'  file 将第7域被3除,并将余数赋给第7域再打印。
11、awk '/tom/ {wage=$2+$3; printf wage}' file 找到匹配行后为变量wage赋值并打印该变量。
12、awk '/tom/ {count++;}
         END {print "tom was found "count" times"}' file END表示在所有输入行处理完后进行处理。
13、awk 'gsub(/\$/,"");gsub(/,/,""); cost+=$4;
         END {print "The total is $" cost>"filename"}'    file gsub函数用空串替换$和,再将结果输出到filename中。
    1 2 3 $1,200.00
    1 2 3 $2,300.00
    1 2 3 $4,000.00

    awk '{gsub(/\$/,"");gsub(/,/,"");
    if ($4>1000&&$4<2000) c1+=$4;
    else if ($4>2000&&$4<3000) c2+=$4;
    else if ($4>3000&&$4<4000) c3+=$4;
    else c4+=$4; }
    END {printf  "c1=[%d];c2=[%d];c3=[%d];c4=[%d]\n",c1,c2,c3,c4}"' file
    通过if和else if完成条件语句

    awk '{gsub(/\$/,"");gsub(/,/,"");
    if ($4>3000&&$4<4000) exit;
    else c4+=$4; }
    END {printf  "c1=[%d];c2=[%d];c3=[%d];c4=[%d]\n",c1,c2,c3,c4}"' file
    通过exit在某条件时退出,但是仍执行END操作。
    awk '{gsub(/\$/,"");gsub(/,/,"");
    if ($4>3000) next;
    else c4+=$4; }
    END {printf  "c4=[%d]\n",c4}"' file
    通过next在某条件时跳过该行,对下一行执行操作。


14、awk '{ print FILENAME,$0 }' file1 file2 file3>fileall 把file1、file2、file3的文件内容全部写到fileall中,格式为
    打印文件并前置文件名。
15、awk ' $1!=previous { close(previous); previous=$1 }   
    {print substr($0,index($0," ") +1)>$1}' fileall 把合并后的文件重新分拆为3个文件。并与原文件一致。
16、awk 'BEGIN {"date"|getline d; print d}'         通过管道把date的执行结果送给getline,并赋给变量d,然后打印。
17、awk 'BEGIN {system("echo \"Input your name:\\c\""); getline d;print "\nYour name is",d,"\b!\n"}'
    通过getline命令交互输入name,并显示出来。
    awk 'BEGIN {FS=":"; while(getline< "/etc/passwd" >0) { if($1~"050[0-9]_") print $1}}'
    打印/etc/passwd文件中用户名包含050x_的用户名。

18、awk '{ i=1;while(i<NF) {print NF,$i;i++}}' file 通过while语句实现循环。
    awk '{ for(i=1;i<NF;i++) {print NF,$i}}'   file 通过for语句实现循环。   
    type file|awk -F "/" '
    { for(i=1;i<NF;i++)
    { if(i==NF-1) { printf "%s",$i }
    else { printf "%s/",$i } }}'               显示一个文件的全路径。
    用for和if显示日期
    awk  'BEGIN {
for(j=1;j<=12;j++)
{ flag=0;
  printf "\n%d月份\n",j;
        for(i=1;i<=31;i++)
        {
        if (j==2&&i>28) flag=1;
        if ((j==4||j==6||j==9||j==11)&&i>30) flag=1;
        if (flag==0) {printf "%02d%02d ",j,i}
        }
}
}'
19、在awk中调用系统变量必须用单引号,如果是双引号,则表示字符串
Flag=abcd
awk '{print '$Flag'}'   结果为abcd
awk '{print  "$Flag"}'   结果为$Flag

[ Last edited by 无奈何 on 2006-10-27 at 02:42 AM ]
作者: 无奈何     时间: 2006-10-27 02:20    标题: awk执行行操作及怎样从文本文件和字符串中抽取信息

转贴注:又是一篇找不到作者的好文。
[b]awk执行行操作及怎样从文本文件和字符串中抽取信息(一)[/b]
下面没有讲述a w k的全部特性,也不涉及a w k的深层次编程,仅讲述使用a w k执行行操作及怎样从文本文件和字符串中抽取信息。

引用:
内容有:
· 抽取域。
· 匹配正则表达式。
· 比较域。
· 向a w k传递参数。
· 基本的a w k行操作和脚本。

a w k语言的最基本功能是在文件或字符串中基于指定规则浏览和抽取信息。a w k抽取信息后,才能进行其他文本操作。完整的a w k脚本通常用来格式化文本文件中的信息。
下面没有讲述a w k的全部特性,也不涉及a w k的深层次编程,仅讲述使用a w k执行行操作及怎样从文本文件和字符串中抽取信息。

引用:
内容有:
· 抽取域。
· 匹配正则表达式。
· 比较域。
· 向a w k传递参数。
· 基本的a w k行操作和脚本。

a w k语言的最基本功能是在文件或字符串中基于指定规则浏览和抽取信息。a w k抽取信息后,才能进行其他文本操作。完整的a w k脚本通常用来格式化文本文件中的信息。

1 调用awk

有三种方式调用a w k,第一种是命令行方式,如:
代码:
awk [-F fild-separator] 'commands' input-file(s)

这里,c o m m a n d s是真正的a w k命令。
上面例子中, [ - F域分隔符]是可选的,因为a w k使用空格作为缺省的域分隔符,因此如果要浏览域间有空格的文本,不必指定这个选项,但如果要浏览诸如p a s s w d文件,此文件各域以冒号作为分隔符,则必须指明- F选项,如:
代码:
awk -F:  'commands' input-file(s)

第二种方法是将所有a w k命令插入一个文件,并使a w k程序可执行,然后用a w k命令解释器作为脚本的首行,以便通过键入脚本名称来调用它。

第三种方式是将所有的a w k命令插入一个单独文件,然后调用:
代码:
awk -f awk-script-file input-files(s)

- f选项指明在文件a w k _ s c r i p t _ f i l e中的a w k脚本, i n p u t _ f i l e ( s )是使用a w k进行浏览的文件名。

2 awk脚本
在命令中调用a w k时,a w k脚本由各种操作和模式组成。
如果设置了- F选项,则a w k每次读一条记录或一行,并使用指定的分隔符分隔指定域,但如果未设置- F选项,a w k假定空格为域分隔符,并保持这个设置直到发现一新行。当新行出现时,a w k命令获悉已读完整条记录,然后在下一个记录启动读命令,这个读进程将持续到文件尾或文件不再存在。

参照表,a w k每次在文件中读一行,找到域分隔符(这里是符号#),设置其为域n,直至一新行(这里是缺省记录分隔符),然后,划分这一行作为一条记录,接着a w k再次启动下一行读进程。
awk读文件记录的方式
引用:
域1 分隔符 域2 分隔符 域3 分隔符 域4及换行
P. B u n n y (记录1 ) # 0 2 / 9 9 # 4 8 # Yellow \n
J . Tr o l l (记录2 ) # 0 7 / 9 9 # 4 8 4 2 # Brown-3 \n

2.1 模式和动作
任何a w k语句都由模式和动作组成。在一个a w k脚本中可能有许多语句。模式部分决定动作语句何时触发及触发事件。处理即对数据进行的操作。如果省略模式部分,动作将时刻保持执行状态。
模式可以是任何条件语句或复合语句或正则表达式。模式包括两个特殊字段B E G I N和E N D。使用B E G I N语句设置计数和打印头。B E G I N语句使用在任何文本浏览动作之前,之后文本浏览动作依据输入文件开始执行。E N D语句用来在a w k完成文本浏览动作后打印输出文本总数和结尾状态标志。如果不特别指明模式, a w k总是匹配或打印行数。
实际动作在大括号{ }内指明。动作大多数用来打印,但是还有些更长的代码诸如i f和循环(l o o p i n g)语句及循环退出结构。如果不指明采取动作, a w k将打印出所有浏览出来的记录。

2. 域和记录
a w k执行时,其浏览域标记为$ 1,$ 2 . . . $ n。这种方法称为域标识。使用这些域标识将更容易对域进行进一步处理。
使用$ 1 , $ 3表示参照第1和第3域,注意这里用逗号做域分隔。如果希望打印一个有5个域的记录的所有域,不必指明$ 1 , $ 2 , $ 3 , $ 4 , $ 5,可使用$ 0,意即所有域。Aw k浏览时,到达一新行,即假定到达包含域的记录末尾,然后执行新记录下一行的读动作,并重新设置域分隔。
注意执行时不要混淆符号$和s h e l l提示符$,它们是不同的。
为打印一个域或所有域,使用p r i n t命令。这是一个a w k动作(动作语法用圆括号括起来)。

1. 抽取域
真正执行前看几个例子,现有一文本文件g r a d e . t x t,记录了一个称为柔道数据库的行信息。
代码:
$ cat grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99   4712 Brown-2 12 30 28

此文本文件有7个域,即(1)名字、(2)升段日期、(3)学生序号、(4)腰带级别、(5)年龄、(6)目前比赛积分、(7)比赛最高分。
因为域间使用空格作为域分隔符,故不必用- F选项划分域,现浏览文件并导出一些数据。在例子中为了利于显示,将空格加宽使各域看得更清晰。

2. 保存a w k输出
有两种方式保存s h e l l提示符下a w k脚本的输出。最简单的方式是使用输出重定向符号>文件名,下面的例子重定向输出到文件w o w。
代码:
$ awk '{print }' grade.txt >wow
$ cat grade.txt

使用这种方法要注意,显示屏上不会显示输出结果。因为它直接输出到文件。只有在保证输出结果正确时才会使用这种方法。它也会重写硬盘上同名数据。

第二种方法是使用t e e命令,在输出到文件的同时输出到屏幕。在测试输出结果正确与否时多使用这种方法。例如输出重定向到文件d e l e t e _ m e _ a n d _ d i e,同时输出到屏幕。使用这种方法,在a w k命令结尾写入| tee delete_me_and_die。
代码:
$ awk '{print }' grade.txt | tee delete_me_and_die

3. 使用标准输入
在深入讲解这一章之前,先对a w k脚本的输入方法简要介绍一下。实际上任何脚本都是从标准输入中接受输入的。为运行本章脚本,使用a w k脚本输入文件格式,例如:
引用:
belts.awk grade_student.txt
也可替代使用下述格式:
使用重定向方法:
belts.awk < grade2.txt
或管道方法:
grade2.txt | belts.awk

这里我怎么看不明白,汗

4. 打印所有记录
代码:
$ awk '{print }' grade.txt

a w k读每一条记录。因为没有模式部分,只有动作部分{print }(打印所有记录),这个动作必须用花括号括起来。上述命令打印整个文件。

5. 打印单独记录
假定只打印学生名字和腰带级别,通过查看域所在列,可知为f i e l d - 1和f i e l d - 4,因此可以使用$ 1和$ 4,但不要忘了加逗号以分隔域。
代码:
$ awk '{print ,}' grade.txt
M.Tans Green
J.Lulu green
P.Bunny Yellow
J.Troll Brown-3
L.Tansl Brown-2

6. 打印报告头
上述命令输出在名字和腰带级别之间用一些空格使之更容易划分,也可以在域间使用t a b键加以划分。为加入t a b键,使用t a b键速记引用符\ t,后面将对速记引用加以详细讨论。也可以为输出文本加入信息头。本例中加入n a m e和b e l t及下划线。下划线使用\ n,强迫启动新行,并在\ n下一行启动打印文本操作。打印信息头放置在B E G I N模式部分,因为打印信息头被界定为一个动作,必须用大括号括起来。在a w k查看第一条记录前,信息头被打印。
代码:
$ awk 'BEGIN {print "Name Belt\n-----------------------------------"}{print "\t",}' grade.txt
Name Belt
-----------------------------------
M.Tans   Green
J.Lulu   green
P.Bunny  Yellow
J.Troll  Brown-3
L.Tansl  Brown-2

7. 打印信息尾
如果在末行加入end of report信息,可使用E N D语句。E N D语句在所有文本处理动作执行完之后才被执行。E N D语句在脚本中的位置放置在主要动作之后。下面简单打印头信息并告之查询动作完成。
代码:
$ awk 'BEGIN {print "Name\n--------"}{print } END ' grade.txt
Name
--------
M.Tans
J.Lulu
P.Bunny
J.Troll
L.Tansl

8. awk错误信息提示
几乎可以肯定,在使用a w k时,将会在命令中碰到一些错误。a w k将试图打印错误行,但由于大部分命令都只在一行,因此帮助不大。
系统给出的显示错误信息提示可读性不好。使用上述例子,如果丢了一个双引号, a w k将返回:
代码:
$ awk 'BEGIN {print "Name\n--------}{print } END ' grade.txt
awk: cmd. line:1: BEGIN {print "Name\n--------}{print } END
awk: cmd. line:1:                                                            ^ unterminated string

当第一次使用a w k时,可能被错误信息搅得不知所措,但通过长时间和不断的学习,可总结出以下规则。在碰到a w k错误时,可相应查找:
引用:
· 确保整个a w k命令用单引号括起来。
· 确保命令内所有引号成对出现。
· 确保用花括号括起动作语句,用圆括号括起条件语句。
· 可能忘记使用花括号,也许你认为没有必要,但a w k不这样认为,将按之解释语法

如果查询文件不存在,将得到下述错误信息:
代码:
$ awk 'END {print NR}' grades.txt
awk: cmd. line:2: fatal: cannot open file `grades.txt' for reading (没有那个文件或目录)

9.awk 键盘输入
如果在命令行并没有输入文件g r a d e . t x t,将会怎样?
代码:
$ awk 'BEGIN {print "Name\n--------"}{print } END '
Name
--------

B E G I N部分打印了文件头,但a w k最终停止操作并等待,并没有返回s h e l l提示符。这是因为a w k期望获得键盘输入。因为没有给出输入文件, a w k假定下面将会给出。如果愿意,顺序输入相关文本,并在输入完成后敲<Ct r l - D >键。如果敲入了正确的域分隔符, a w k会像第一个例子一样正常处理文本。这种处理并不常用,因为它大多应用于大量的打印稿。

2.3awk中正则表达式及其操作

在g r e p一章中,有许多例子用到正则表达式,这里将不使用同样的例子,但可以使用条件操作讲述a w k中正则表达式的用法。
这里正则表达式用斜线括起来。例如,在文本文件中查询字符串G r e e n,使用/ G r e e n /可以查出单词G r e e n的出现情况。

2.4元字符
这里是a w k中正则表达式匹配操作中经常用到的字符,详细情况请参阅本书第7章正则表达式概述。
代码:
\ ^ $ . [] | () * + ?

这里有两个字符第7章没有讲到,因为它们只适用于a w k而不适用于g r e p或s e d。它们是:
引用:
+ 使用+匹配一个或多个字符。
? 匹配模式出现频率。例如使用/X Y?Z/匹配X Y Z或Y Z。

==========================================================

[b]awk执行行操作及怎样从文本文件和字符串中抽取信息(二)[/b]

1.awk条件操作符
2.awk内置变量
3.awk操作符
4.内置的字符串函数
-------------------

awk条件操作符

操作符描述
< 小于
> = 大于等于
< = 小于等于
== 等于
!= 不等于
~ 匹配正则表达式
!~ 不匹配正则表达式

1. 匹配
为使一域号匹配正则表达式,使用符号'~'后紧跟正则表达式,也可以用if语句。awk中if后面的条件用()括起来。
观察文件grade.txt,如果只要显示brown腰带级别可知其所在域为field-4,这样可以写出表达式{if(~/Brown/) print}意即如果field-4包含brown,打印它。如果条件满足,则打印匹配记录行。可以编写下面脚本,因为这是一个动作,必须用花括号{}括起来。 代码:
[root@Linux_chenwy sam]# awk '{if(~/Brown/) print }' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28

匹配记录找到时,如果不特别声明,awk缺省打印整条记录。使用if语句开始有点难,但不要着急,因为有许多方法可以跳过它,并仍保持同样结果。下面例子意即如果记录包含模式brown,就打印它: 代码:
[root@Linux_chenwy sam]# awk '~/Brown/' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28

2. 精确匹配
假定要使字符串精确匹配,比如说查看学生序号48,文件中有许多学生序号包含48,如果在field-3中查询序号48,awk将返回所有序号带48的记录:
代码:
[root@Linux_chenwy sam]# awk '{if(~/48/) print}' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26

为精确匹配48,使用等号==,并用单引号括起条件。例如=="48" 代码:
[root@Linux_chenwy sam]# awk '=="48"' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28
[root@Linux_chenwy sam]# awk '{if(=="48") print}' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28

3. 不匹配
有时要浏览信息并抽取不匹配操作的记录,与~相反的符号是!~,意即不匹配。像原来使用查询brown腰带级别的匹配操作一样,现在看看不匹配情况。表达式!~/Brown/,意即查询不包含模式brown腰带级别的记录并打印它。
注意,缺省情况下,awk将打印所有匹配记录,因此这里不必加入动作部分。
代码:
[root@Linux_chenwy sam]# awk '!~/Brown/' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28

可以只对field-4进行不匹配操作,方法如下: 代码:
[root@Linux_chenwy sam]# awk '{if(!~/Brown/) print }' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28

如果只使用命令awk !="brown"{print } grade.txt,将返回错误结果,因为用引号括起了brown,将只匹配‘brown而不匹配brown-2和brown-3,当然,如果想要查询非brown-2的腰带级别,可做如下操作: 代码:
[root@Linux_chenwy sam]# awk '!="Brown-2" {print }' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26

4. 小于
看看哪些学生可以获得升段机会。测试这一点即判断目前级别分field-6是否小于最高分field-7,在输出结果中,加入这一改动很容易。
代码:
[root@Linux_chenwy sam]# awk '{if(<) print }' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26

5. 小于等于
对比小于,小于等于只在操作符上做些小改动,满足此条件的记录也包括上面例子中的输出情况。
代码:
[root@Linux_chenwy sam]# awk '{if( <= ) print }' grade.txt
M.Tans
J.Lulu
J.Troll

6. 大于
代码:
[root@Linux_chenwy sam]# awk '{if( > ) print }' grade.txt
P.Bunny
L.Tansl

7. 设置大小写
为查询大小写信息,可使用[ ]符号。在测试正则表达式时提到可匹配[ ]内任意字符或单词,因此若查询文件中级别为green的所有记录,不论其大小写,表达式应为'/[Gg]reen/'
代码:
[root@Linux_chenwy sam]# awk '/[Gg]reen/' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26

8. 任意字符
抽取名字,其记录第一域的第四个字符是a,使用句点.。表达式/^...a/意为行首前三个字符任意,第四个是a,尖角符号代表行首。
代码:
[root@Linux_chenwy sam]# awk ' ~ /^...a/' grade.txt
M.Tans 5/99 48311 Green 8 40 44
L.Tansl 05/99 4712 Brown-2 12 30 28

9. 或关系匹配
为抽取级别为yellow或brown的记录,使用竖线符|。意为匹配|两边模式之一。注意,使用竖线符时,语句必须用圆括号括起来。 代码:
[root@Linux_chenwy sam]# awk ' ~/(Yellow|Brown)/' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28

上面例子输出所有级别为Ye l l o w或B r o w n的记录。

使用这种方法在查询级别为G r e e n或g r e e n时,可以得到与使用[ ]表达式相同的结果。
代码:
[root@Linux_chenwy sam]# awk '/^M/' grade.txt
M.Tans 5/99 48311 Green 8 40 44

10. 行首
不必总是使用域号。如果查询文本文件行首包含M的代码,可简单使用下面^符号:
代码:
[root@Linux_chenwy sam]# awk '/^M/' grade.txt

复合表达式即为模式间通过使用下述各表达式互相结合起来的表达式:
引用:
&& AND : 语句两边必须同时匹配为真。
|| O R:语句两边同时或其中一边匹配为真。
! 非求逆

11. AND
打印记录,使其名字为‘P.Bunny且级别为Yellow,使用表达式(=="P.Bunny" && =="Yellow" ),意为&&两边匹配均为真。完整命令如下:
代码:
[root@Linux_chenwy sam]# awk '{if (=="P.Bunny" && =="Yellow") print }' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28

12. Or
如果查询级别为Yellow或Brown,使用或命令。意为"||"符号两边的匹配模式之一或全部为真。 代码:
[root@Linux_chenwy sam]# awk '{if (=="Yellow" || ~/Brown/) print }' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28

原来不一定得加print,下面我自己对例一二做了一下
代码:
1
[root@Linux_chenwy sam]# awk '~/Brown/' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28

代码:
2
[root@Linux_chenwy sam]# awk '=="48"' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28

代码:
[root@Linux_chenwy sam]# awk '="48"' grade.txt
M.Tans 5/99 48 Green 8 40 44
J.Lulu 06/99 48 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 48 Brown-3 12 26 26
L.Tansl 05/99 48 Brown-2 12 30 28

2中,我把=和==写错了,呵呵,一个是赋值,一个是等于

awk内置变量
awk有许多内置变量用来设置环境信息。这些变量可以被改变。表9-3显示了最常使用的一些变量,并给出其基本含义。 引用:
awk内置变量

ARGC 命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FILENAME awk浏览的文件名
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行- F选项
NF 浏览记录的域个数
NR 已读的记录数
OFS 输出域分隔符
ORS 输出记录分隔符
RS 控制记录分隔符

引用:
A R G C支持命令行中传入a w k脚本的参数个数。A R G V是A R G C的参数排列数组,其中每一元素表示为A R G V [ n ],n为期望访问的命令行参数。

E N V I R O N 支持系统设置的环境变量,要访问单独变量,使用实际变量名,例如E N V I R O N [“E D I TO R”] =“Vi”。

F I L E N A M E支持a w k脚本实际操作的输入文件。因为a w k可以同时处理许多文件,因此如果访问了这个变量,将告之系统目前正在浏览的实际文件。

F N R支持a w k目前操作的记录数。其变量值小于等于N R。如果脚本正在访问许多文件,每一新输入文件都将重新设置此变量。

F S用来在a w k中设置域分隔符,与命令行中- F选项功能相同。缺省情况下为空格。如果用逗号来作域分隔符,设置F S = ","。

N F支持记录域个数,在记录被读之后再设置。

O F S允许指定输出域分隔符,缺省为空格。如果想设置为#,写入O F S = " # "。

O R S为输出记录分隔符,缺省为新行( \ n)。

R S是记录分隔符,缺省为新行( \ n )。

NF、NR和FILENAME

要快速查看记录个数,应使用N R。比如说导出一个数据库文件后,如果想快速浏览记录个数,以便对比于其初始状态,查出导出过程中出现的错误。使用N R将打印输入文件的记录个数。print NR放在E N D语法中。
代码:
[root@chenwy sam]# awk 'END{print NR}' grade.txt
5

如:所有学生记录被打印,并带有其记录号。使用N F变量显示每一条读记录中有多少个域,并在E N D部分打印输入文件名。
[root@chenwy sam]# awk '{print NF,NR,} END{print FILENAME}' grade.txt
代码:
7 1 M.Tans 5/99 48311 Green 8 40 44
7 2 J.Lulu 06/99 48317 green 9 24 26
7 3 P.Bunny 02/99 48 Yellow 12 35 28
7 4 J.Troll 07/99 4842 Brown-3 12 26 26
7 5 L.Tansl 05/99       4712 Brown-2 12 30 28
grade.txt

在从文件中抽取信息时,最好首先检查文件中是否有记录。下面的例子只有在文件中至少有一个记录时才查询B r o w n级别记录。使用A N D复合语句实现这一功能。意即至少存在一个记录后,查询字符串B r o w n,最后打印结果。
代码:
[root@chenwy sam]# awk '{if (NR>0 && ~/Brown/)print }' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99   4712 Brown-2 12 30 28

N F的一个强大功能是将变量$ P W D的返回值传入a w k并显示其目录。这里需要指定域分隔符/。
代码:
[root@chenwy sam]# echo $PWD | awk -F/ ' {print $NF}'
sam

另一个例子是显示文件名。
代码:
[root@chenwy sam]# echo "/usr/local/etc/rc.sybase" | awk -F/ '{print $NF}'
rc.sybase

如果不指定域分割符,返回的如下:
代码:
[root@chenwy sam]# echo $PWD | awk  '{print $NF}'
/usr/sam
[root@chenwy sam]# echo "/usr/local/etc/rc.sybase" | awk '{print $NF}'
/usr/local/etc/rc.sybase

awk操作符
在a w k中使用操作符,基本表达式可以划分为数字型、字符串型、变量型、域及数组元素,前面已经讲过一些。下面列出其完整列表。

在表达式中可以使用下述任何一种操作符。

引用:
= += *= / = %= ^ = 赋值操作符
? 条件表达操作符
|| && ! 并、与、非(上一节已讲到)
~!~ 匹配操作符,包括匹配和不匹配
< <= == != >> 关系操作符
+ - * / % ^ 算术操作符
+ + -- 前缀和后缀

前面已经讲到了其中几种操作,下面继续讲述未涉及的部分。

1. 设置输入域到域变量名
在a w k中,设置有意义的域名是一种好习惯,在进行模式匹配或关系操作时更容易理解。
一般的变量名设置方式为n a m e = $ n,这里n a m e为调用的域变量名, n为实际域号。例如设置学生域名为n a m e,级别域名为b e l t,操作为n a m e = $ 1 ; b e l t s = $ 4。注意分号的使用,它分隔a w k命令。下面例子中,重新赋值学生名域为n a m e,级别域为b e l t s。查询级别为Ye l l o w的记录,并最终打印名称和级别。
代码:
[sam@chenwy sam]$ awk '{name=;belts=;if(belts ~/Yellow/) print name" is belt "belts}' grade.txt
P.Bunny is belt Yellow

2. 域值比较操作
有两种方式测试一数值域是否小于另一数值域。
1) 在B E G I N中给变量名赋值。
2) 在关系操作中使用实际数值。
通常在B E G I N部分赋值是很有益的,可以在a w k表达式进行改动时减少很多麻烦。
使用关系操作必须用圆括号括起来。
下面的例子查询所有比赛中得分在2 7点以下的学生。
用引号将数字引用起来是可选的,“2 7”、2 7产生同样的结果。
代码:
[sam@chenwy sam]$ awk '{if (<) print }' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26

第二个例子中给数字赋以变量名B A S E L I N E和在B E G I N部分给变量赋值,两者意义相同。
代码:
[sam@chenwy sam]$ awk 'BEGIN {if (J.Lulu 06/99 48317 green 9 24 26
J.Troll 07/99 4842 Brown-3 12 26 26

3. 修改数值域取值
当在a w k中修改任何域时,重要的一点是要记住实际输入文件是不可修改的,修改的只是保存在缓存里的a w k复本。a w k会在变量N R或N F变量中反映出修改痕迹。
为修改数值域,简单的给域标识重赋新值,如: $ 1 = $ 1 + 5,会将域1数值加5,但要确保赋值域其子集为数值型。
修改M . Ta n s l e y的目前级别分域,使其数值从4 0减为3 9,使用赋值语句$ 6 = $ 6 - 1,当然在实施修改前首先要匹配域名。
代码:
[sam@chenwy sam]$ awk '{if(=="M.Tans") ;print ,,}' grade.txt
M.Tans 39 44
J.Lulu 24 26
P.Bunny 35 28
J.Troll 26 26
L.Tansl 30 28

代码:
[sam@chenwy sam]$ awk '{if(=="M.Tans") {=-1;print ,,}}' grade.txt
M.Tans 39 44

4. 修改文本域
修改文本域即对其重新赋值。需要做的就是赋给一个新的字符串。在J . Tr o l l中加入字母,使其成为J . L . Tr o l l,表达式为$ 1 = " J . L . Tr o l l ",记住字符串要使用双秒号( " "),并用圆括号括起整个语法。
代码:
[sam@chenwy sam]$ awk '{if(=="J.Troll") ="J.L.Troll"; print }' grade.txt
M.Tans
J.Lulu
P.Bunny
J.L.Troll
L.Tansl

5. 只显示修改记录
上述例子均是对一个小文件的域进行修改,因此打印出所有记录查看修改部分不成问题,但如果文件很大,记录甚至超过1 0 0,打印所有记录只为查看修改部分显然不合情理。在模式后面使用花括号将只打印修改部分。取得模式,再根据模式结果实施操作,可能有些抽象,现举一例,只打印修改部分。注意花括号的位置。
代码:
[sam@chenwy sam]$ awk '{if(=="J.Troll") {="J.L.Troll"; print }}' grade.txt
J.L.Troll

不知道为什么,我这里多了一个空行?

6. 创建新的输出域
在a w k中处理数据时,基于各域进行计算时创建新域是一种好习惯。创建新域要通过其他域赋予新域标识符。如创建一个基于其他域的加法新域{ $ 4 = $ 2 + $ 3 },这里假定记录包含3个域,则域4为新建域,保存域2和域3相加结果。
在文件g r a d e . t x t中创建新域8保存域目前级别分与域最高级别分的减法值。表达式为‘{ $ 8 = $ 7 - $ 6 }’,语法首先测试域目前级别分小于域最高级别分。新域因此只打印其值大于零的学生名称及其新域值。在B E G I N部分加入t a b键以对齐报告头。
代码:
[sam@chenwy sam]$ awk 'BEGIN{print "Name\tDifference"}{if(<) {=-;print ,}}' grade.txt
Name    Difference
M.Tans 4
J.Lulu 2

当然可以创建新域,并赋给其更有意义的变量名。例如:
代码:
[sam@chenwy sam]$ awk 'BEGIN{print "Name\tDifference"}{if(<) {diff=-;print ,diff}}' grade.txt
Name    Difference
M.Tans 4
J.Lulu 2

7. 增加列值
为增加列数或进行运行结果统计,使用符号+ =。增加的结果赋给符号左边变量值,增加到变量的域在符号右边。例如将$ 1加入变量t o t a l,表达式为t o t a l + = $ 1。列值增加很有用。许多文件都要求统计总数,但输出其统计结果十分繁琐。在a w k中这很简单,请看下面的例子。
将所有学生的‘目前级别分’加在一起,方法是t o t + = $ 6,t o t即为a w k浏览的整个文件的域6结果总和。所有记录读完后,在E N D部分加入一些提示信息及域6总和。不必在a w k中显示说明打印所有记录,每一个操作匹配时,这是缺省动作。
代码:
[sam@chenwy sam]$ awk '(tot+=); END{print "Club student total points :" tot}'
grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28
Club student total points :155

如果文件很大,你只想打印结果部分而不是所有记录,在语句的外面加上圆括号()即可。
代码:
[sam@chenwy sam]$ awk '; END{print "Club student total points :" tot}' grade.txt
Club student total points :155

8. 文件长度相加
在目录中查看文件时,如果想快速查看所有文件的长度及其总和,但要排除子目录,使用ls -l命令,然后管道输出到a w k,a w k首先剔除首字符为d(使用正则表达式)的记录,然后将文件长度列相加,并输出每一文件长度及在E N D部分输出所有文件的长度。
本例中,首先用ls -l命令查看一下文件属性。注意第二个文件属性首字符为d,说明它是一个目录,文件长度是第5列,文件名是第9列。如果系统不是这样排列文件名及其长度,应适时加以改变。
下面的正则表达式表明必须匹配行首,并排除字符d,表达式为^ [ ^ d ]。
使用此模式打印文件名及其长度,然后将各长度相加放入变量t o t中。
代码:
[sam@chenwy sam]$ ls -l | awk '/^[^d]/ {print "\t"} END {print "total KB:" tot}'
...................
total KB:174144

内置的字符串函数

代码:
awk内置字符串函数
g s u b ( r, s ) 在整个$ 0中用s替代r
g s u b ( r, s , t ) 在整个t中用s替代r
i n d e x ( s , t ) 返回s中字符串t的第一位置
l e n g t h ( s ) 返回s长度
m a t c h ( s , r ) 测试s是否包含匹配r的字符串
s p l i t ( s , a , f s ) 在f s上将s分成序列a
s p r i n t ( f m t , e x p ) 返回经f m t格式化后的e x p
s u b ( r, s ) 用$ 0中最左边最长的子串代替s
s u b s t r ( s , p ) 返回字符串s中从p开始的后缀部分
s u b s t r ( s , p , n ) 返回字符串s中从p开始长度为n的后缀部分

g s u b函数有点类似于s e d查找和替换。它允许替换一个字符串或字符为另一个字符串或字符,并以正则表达式的形式执行。第一个函数作用于记录$ 0,第二个g s u b函数允许指定目标,然而,如果未指定目标,缺省为$ 0。
i n d e x(s,t)函数返回目标字符串s中查询字符串t的首位置。l e n g t h函数返回字符串s字符长度。
m a t c h函数测试字符串s是否包含一个正则表达式r定义的匹配。s p l i t使用域分隔符f s将字符串s划分为指定序列a。
s p r i n t函数类似于p r i n t f函数(以后涉及),返回基本输出格式f m t的结果字符串e x p。
s u b(r,s)函数将用s替代$ 0中最左边最长的子串,该子串被( r)匹配。
s u b(s,p)返回字符串s在位置p后的后缀。s u b s t r(s,p,n)同上,并指定子串长度为n。
现在看一看a w k中这些字符串函数的功能。

1. gsub
要在整个记录中替换一个字符串为另一个,使用正则表达式格式, /目标模式/,替换模式/。例如改变学生序号4 8 4 2到4 8 9 9:
代码:
[root@Linux_chenwy root]# cd /usr/sam
[root@Linux_chenwy sam]# awk 'gsub(/4842/,4899){print }' grade.txt
J.Troll 07/99 4899 Brown-3 12 26 26

代码:
[root@Linux_chenwy sam]# awk 'gsub(/4842/,4899)' grade.txt
J.Troll 07/99 4899 Brown-3 12 26 26

2. index
查询字符串s中t出现的第一位置。必须用双引号将字符串括起来。例如返回目标字符串B u n n y中n y出现的第一位置,即字符个数。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN {print index("Bunny","ny")}' grade.txt
4

3. length
返回所需字符串长度,例如检验字符串J . Tr o l l返回名字及其长度,即人名构成的字符个数
代码:
[root@Linux_chenwy sam]# awk '=="J.Troll" {print length()" "}' grade.txt
7 J.Troll

还有一种方法,这里字符串加双引号。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN{print length("A FEW GOOD MEN")}'
14

4. match
m a t c h测试目标字符串是否包含查找字符的一部分。可以对查找部分使用正则表达式,返回值为成功出现的字符排列数。如果未找到,返回0,第一个例子在A N C D中查找d。因其不存在,所以返回0。第二个例子在A N C D中查找D。因其存在,所以返回A N C D中D出现的首位置字符数。第三个例子在学生J . L u l u中查找u。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN{print match("ANCD",/d/)}'
0
[root@Linux_chenwy sam]# awk 'BEGIN{print match("ANCD",/D/)}'
4
[root@Linux_chenwy sam]# awk '=="J.Lulu" {print match(,"u")}' grade.txt
4

5. split
使用s p l i t返回字符串数组元素个数。工作方式如下:如果有一字符串,包含一指定分隔符- ,例如A D2 - K P 9 - J U 2 - L P - 1,将之划分成一个数组。使用s p l i t,指定分隔符及数组名。此例中,命令格式为( " A D 2 - K P 9 - J U 2 - L P - 1 ",p a r t s _ a r r a y," - "),s p l i t然后返回数组下标数,这里结果为4。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN {print split("123-456-789",pats_array,"-")}'3

还有一个例子使用不同的分隔符。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN {print split("123#456#789",myarray,"#")}'                                                        3

这个例子中,s p l i t返回数组m y a r r a y的下标数。数组m y a r r a y取值如下:
代码:
myarray[1]=123
myarray[2]=456
myarray[3]=789

结尾部分讲述数组概念。

6. sub
使用s u b发现并替换模式的第一次出现位置。字符串S T R包含‘poped popo pill’,执行下列s u b命令s u b(/ o p /," o p ",S T R)。模式o p第一次出现时,进行替换操作,返回结果如下:‘pO Ped pope pill’。

如:学生J . Tr o l l的记录有两个值一样,“目前级别分”与“最高级别分”。只改变第一个为2 9,第二个仍为2 4不动,操作命令为s u b(/ 2 6 /," 2 9 ",$ 0),只替换第一个出现2 4的位置。注意J . Tr o l l记录需存在。
代码:
[root@Linux_chenwy sam]# awk '=="J.Troll" sub(/26/,"29",)' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 29
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 29 26
L.Tansl 05/99 4712 Brown-2 12 30 28

7. substr
s u b s t r是一个很有用的函数。它按照起始位置及长度返回字符串的一部分。例子如下:
代码:
[root@Linux_chenwy sam]# awk '=="L.Tansl" {print substr(,1,3)}' grade.txt
L.T

上面例子中,指定在域1的第一个字符开始,返回其前面5个字符。

如果给定长度值远大于字符串长度, a w k将从起始位置返回所有字符,要抽取L Ta n s l - e y的姓,只需从第3个字符开始返回长度为7。可以输入长度9 9,a w k返回结果相同。
代码:
[root@Linux_chenwy sam]# awk '=="L.Tansl" {print substr(,1,99)}' grade.txt
L.Tansl

s u b s t r的另一种形式是返回字符串后缀或指定位置后面字符。这里需要给出指定字符串及其返回字串的起始位置。例如,从文本文件中抽取姓氏,需操作域1,并从第三个字符开始:
代码:
[root@Linux_chenwy sam]# awk '{print substr(,3)}' grade.txt
Tans
Lulu
Bunny
Troll
Tansl

还有一个例子,在B E G I N部分定义字符串,在E N D部分返回从第t个字符开始抽取的子串。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN{STR="A FEW GOOD MEN"}END{print substr(STR,7)}' grade.txt
GOOD MEN

8. 从s h e l l中向a w k传入字符串
a w k脚本大多只有一行,其中很少是字符串表示的。大多要求在一行内完成a w k脚本,这一点通过将变量传入a w k命令行会变得很容易。现就其基本原理讲
述一些例子。
使用管道将字符串s t a n d - b y传入a w k,返回其长度。
代码:
[root@Linux_chenwy sam]# echo "Stand-by" | awk '{print length()}'
8

设置文件名为一变量,管道输出到a w k,返回不带扩展名的文件名。
代码:
[root@Linux_chenwy sam]# STR="mydoc.txt"
[root@Linux_chenwy sam]# echo $STR|awk '{print substr($STR,1,5)}'
mydoc

设置文件名为一变量,管道输出到a w k,只返回其扩展名。
代码:
[root@Linux_chenwy sam]# STR="mydoc.txt"
[root@Linux_chenwy sam]# echo $STR|awk '{print substr($STR,7)}'
txt


==========================================================
[b]awk执行行操作及怎样从文本文件和字符串中抽取信息(三)[/b]
字符串屏蔽序列

1. 字符转换
2. 格式化输出
3.向一行a w k命令传值
4. awk脚本文件
5. 在awk中使用FS变量
6. 向awk脚本传值

--------------------------------

字符串屏蔽序列

使用字符串或正则表达式时,有时需要在输出中加入一新行或查询一元字符。
打印一新行时,(新行为字符\ n),给出其屏蔽序列,以不失其特殊含义,用法为在字符串前加入反斜线。例如使用\ n强迫打印一新行。
如果使用正则表达式,查询花括号( { }),在字符前加反斜线,如/ \ { /,将在a w k中失掉其特殊含义。

代码:
awk中使用的屏蔽序列
\ b 退格键
\ t t a b键
\ f 走纸换页
\ d d d 八进制值
\ n 新行
\ c 任意其他特殊字符,例如\ \为反斜线符号
\ r 回车键

使用上述符号,打印May Day,中间夹t a b键,后跟两个新行,再打印May Day,但这次使用八进制数1 0 4、1 4 1、1 7 1、分别代表D、a、y。
代码:
[root@chenwy sam]# awk 'BEGIN '

May     Day

May     Day

注意,\ 1 0 4为D的八进制A S C I I码,\ 1 4 1为a的八进制A S C I I码,等等。

awk输出函数printf
目前为止,所有例子的输出都是直接到屏幕,除了t a b键以外没有任何格式。a w k提供函数p r i n t f,拥有几种不同的格式化输出功能。例如按列输出、左对齐或右对齐方式。
每一种p r i n t f函数(格式控制字符)都以一个%符号开始,以一个决定转换的字符结束.转换包含三种修饰符。
p r i n t f函数基本语法是p r i n t f([格式控制符],参数),格式控制字符通常在引号里。

printf修饰符
代码:
- 左对齐
Wi d t h 域的步长,用0表示0步长
. p r e c 最大字符串长度,或小数点右边的位数
表9-7 awk printf格式
% c A S C I I字符
% d 整数
% e 浮点数,科学记数法
% f 浮点数,例如(1 2 3 . 4 4)
% g a w k决定使用哪种浮点数转换e或者f
% o 八进制数
% s 字符串
% x 十六进制数

1. 字符转换
观察A S C I I码中6 5的等价值。管道输出6 5到a w k。p r i n t f进行A S C I I码字符转换。这里也加入换行,因为缺省情况下p r i n t f不做换行动作。
代码:
A[sam@chenwy sam]$ echo "65" | awk '{printf "%c\n",}'
A

按同样方式使用a w k得到同样结果。
代码:
[sam@chenwy sam]$ awk 'BEGIN{printf "%c\n",65}'
A

所有的字符转换都是一样的,下面的例子表示进行浮点数转换后‘ 9 9 9’的输出结果。整数传入后被加了六个小数点。
代码:
[sam@chenwy sam]$ awk 'BEGIN{printf "%f\n",999}'
999.000000

2. 格式化输出
打印所有的学生名字和序列号,要求名字左对齐, 1 5个字符长度,后跟序列号。注意\ n换行符放在最后一个指示符后面。输出将自动分成两列。
代码:
[root@chenwy sam]# awk '{printf "%-15s %s\n",,}' grade.txt
M.Tans          48311
J.Lulu          48317
P.Bunny         48
J.Troll         4842
L.Tansl         4712

加入一些文本注释帮助理解报文含义。可在正文前嵌入头信息。注意这里使用p r i n t加入头信息。如果愿意,也可使用p r i n t f。
代码:
[root@chenwy sam]# awk 'BEGIN{print "Name\t\tS.Number"}{printf "%-15s %s\n",,}' grade.txt
Name            S.Number
M.Tans          48311
J.Lulu          48317
P.Bunny         48
J.Troll         4842
L.Tansl         4712

3.向一行a w k命令传值
在查看a w k脚本前,先来查看怎样在a w k命令行中传递变量。
在a w k执行前将值传入a w k变量,需要将变量放在命令行中,格式如下:
代码:
awk 命令变量=输入文件值

(后面会讲到怎样传递变量到a w k脚本中)。
下面的例子在命令行中设置变量A G E等于1 0,然后传入a w k中,查询年龄在1 0岁以下的所有学生。
代码:
[root@chenwy sam]# awk '{if (M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26

要快速查看文件系统空间容量,观察其是否达到一定水平,可使用下面a w k一行脚本。因为要监视的已使用空间容量不断在变化,可以在命令行指定一个触发值。首先用管道命令将df -k 传入a w k,然后抽出第4列,即剩余可利用空间容量。使用$ 4 ~ / ^ [ 0 - 9 ] /取得容量数值(1 0 2 4块)而不是d f的文件头,然后对命令行与‘ i f ( $ 4 < T R I G G E R )’上变量T R I G G E R中指定
的值进行查询测试。
代码:
[root@chenwy sam]# df -k|awk '{if(/boot   458589
/dev/shm        99352

代码:
[root@chenwy sam]# df -k|awk '(~/^[0-9]/) {if(/       2610716
/boot   458589
/dev/shm        99352

(~/^[0-9]/)好像没什么用

在系统中使用df -k命令,产生下列信息:
代码:
[root@chenwy sam]# df -k
文件系统               1K-块        已用     可用 已用% 挂载点
/dev/sda2              5162828   2289804   2610764  47% /
/dev/sda1               497829     13538    458589   3% /boot
none                     99352         0     99352   0% /dev/shm

如果系统中d f输出格式不同,必须相应改变列号以适应工作系统。
当然可以使用管道将值传入a w k。本例使用w h o命令, w h o命令第一列包含注册用户名,这里打印注册用户,并加入一定信息。
代码:
[sam@chenwy sam]$ who |awk '{print " is logged on"}'
root is logged on
root is logged on
[sam@chenwy sam]$ who
root     :0           Nov 23 20:17
root     pts/0        Nov 23 20:25 (:0.0)

a w k也允许传入环境变量。下面的例子使用环境变量HOME支持当前用户目录。可从pwd命令管道输出到a w k中获得相应信息。
代码:
[sam@chenwy sam]$ pwd | awk '{if (==derr) print }' derr=$HOME
/usr/sam

4. awk脚本文件
可以将a w k脚本写入一个文件再执行它。命令不必很长(尽管这是写入一个脚本文件的主要原因),甚至可以接受一行命令。这样可以保存a w k命令,以使不必每次使用时都需要重新输入。使用文件的另一个好处是可以增加注释,以便于理解脚本的真正用途和功能。
使用前面的几个例子,将之转换成a w k可执行文件。像原来做的一样,将学生目前级别分相加awk ‘(t o t + = $ 6) END{print "club student total points:" t o t }’ g r a d e . t x t。
创建新文件s t u d e n t _ t o t . a w k,给所有a w k程序加入a w k扩展名是一种好习惯,这样通过查看文件名就知道这是一个a w k程序。文本如下:

代码:
[sam@chenwy sam]$ cat student_tot.awk
#!/bin/awk -f
#all commnet lines must start with a hash '#'
#name:students_tots.awk
#to call:student_tot.awk grade.txt
#prints total and average of club student points

#print a header first
BEGIN{
print "Student  Date  Member  No.  Grade Age  Points Max"
print "Name     Joined                        Gained  Point Available"
print "=============================================================="
}
#let's add the scores of points gained
(tot+=)

#finished proessing now let's print the total and average point
END{
print "Club student total points :" tot
print "Average Club Student Points:" tot/NR}

通过将命令分开,脚本可读性提高,还可以在命令之间加入注释。这里加入头
信息和结尾的平均值。基本上这是一个一行脚本文件。
执行时,在脚本文件后键入输入文件名,但是首先要对脚本文件加入可执行权限。
代码:
[sam@chenwy sam]$ chmod u+x student_tot.awk
[sam@chenwy sam]$./student_tot.awk grade.txt
Student  Date  Member  No.  Grade Age  Points Max
Name     Joined                        Gained  Point Available
==============================================================
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28
Club student total points :155
Average Club Student Points:31

过滤相同行:
如有一个文件strip中有多条重复错误提法:
代码:
[sam@Linux_chenwy sam]$ cat strip
etreiytrpytyu
ERROR*
ERROR*
ERROR*
ERROR*
IUEWROPYJRTMELUYK
ERROR*
ERROR*
ERROR*
ERROR*
ERROR*
ERROR*
EWUTIRWJYHT
ERROR*
ERROR*
JGIOERYO56ERU
ERROR*
ERROR*
ERROR*
JGEORYKP65EKU;YK,

现在用a w k脚本过滤出错误行的出现频率,使得每一个失败记录只对应一个错误行。awk脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat error_strip.awk
#!/bin/awk -f
#error_strip.awk
#to call:error_strip.awk
#strips out the ERROR* lines if there are more than one
#ERROR* lines after each failed record.

BEGIN
#tell awk the whole is "ERROR*"
{if (=="ERROR*" && error_line=="ERROR*")

#go to next line
next;
error_line=;print}

执行结果如下:
代码:
[sam@Linux_chenwy sam]$ ./error_strip.awk strip
etreiytrpytyu
ERROR*
IUEWROPYJRTMELUYK
ERROR*
EWUTIRWJYHT
ERROR*
JGIOERYO56ERU
ERROR*
JGEORYKP65EKU;YK,

5. 在a w k中使用F S变量
如果使用非空格符做域分隔符( F S)浏览文件,例如# 或:,编写这样的一行命令很容易,因为使用F S选项可以在命令行中指定域分隔符。
代码:
$awk -F: '{print }' inputfile

使用a w k脚本时,记住设置F S变量是在B E G I N部分。如果不这样做, a w k将会发生混淆,不知道域分隔符是什么。
下述脚本指定F S变量。脚本从/ e t c / p a s s w d文件中抽取第1和第5域,通过分号“;”分隔p a s s w d文件域。第1域是帐号名,第5域是帐号所有者。
我举的例子是第七个域:

代码:
[sam@Linux_chenwy sam]$ awk -F: '{print ,"\t",}' passwd
root     /bin/bash
bin      /sbin/nologin
daemon   /sbin/nologin
adm      /sbin/nologin
lp       /sbin/nologin
sync     /bin/sync
.................................

这是不用脚本的,后面的结果省略

现使用脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat passwd.awk
#!/bin/awk -f
#to call:passwd.awk /etc/passwd
#print out the first and seventh fields
BEGIN{
FS=":"}
{print ,"\t",}

结果如下:
代码:
[sam@Linux_chenwy sam]$ chmod u+x passwd.awk
[sam@Linux_chenwy sam]$ ./passwd.awk passwd
root     /bin/bash
bin      /sbin/nologin
daemon   /sbin/nologin
adm      /sbin/nologin
lp       /sbin/nologin
sync     /bin/sync
.......................................

6. 向a w k脚本传值
向a w k脚本传值与向a w k一行命令传值方式大体相同,格式为:
代码:
awk script_file var=value input_file

下述脚本对比检查文件中域号和指定数字。这里使用了N F变量M A X,表示指定检查的域号,使用双引号将域分隔符括起来,即使它是一个空格。
脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat fieldcheck.awk
#!/bin/awk -f
#check on how many fields in a file
#name:fieldcheck.awk
#to call:fieldcheck MAX=n FS= filename
#
NF!=MAX{
print("line" NR " does not have " MAX "fields")}

如果NF中的值不等于最大MAX值,则打印出"哪一行的域总数不是max"

如果以/ e t c / p a s s w d作输入文件(p a s s w d文件有7个域),运行上述脚本。参数格式如下:
代码:
[sam@Linux_chenwy sam]$ chmod u+x fieldcheck.awk
[sam@Linux_chenwy sam]$ ./fieldcheck.awk MAX=7 FS=":" passwd

正好7个域,如果改成6,就会显示不同结果,试试看?

使用前面一行脚本的例子,将之转换成a w k脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat name.awk
#!/bin/awk -f
#name:age.awk
#to call:age.awk AGE=n grade.txt
#print ages that are lower than the age supplied on the comand line
{if (print }

文本包括了比实际命令更多的信息,没关系,仔细研读文本后,就可以精确知道其功能及如何调用它。
不要忘了增加脚本的可执行权限,然后将变量和赋值放在命令行脚本名字后、输入文件前执行。
代码:
[sam@Linux_chenwy sam]$ chmod u+x name.awk
[sam@Linux_chenwy sam]$ ./name.awk AGE=10 grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26

同样可以使用前面提到的管道命令传值,下述a w k脚本从d u命令获得输入,并输出块和字节数。
代码:
[root@Linux_chenwy sam]# cat duawk.awk
#!/bin/awk -f
#to call:du|duawk.awk
#prints file/direc's in bytes and blocks
BEGIN{
OFS="\t";
print "name" "\t\t","bytes","blocks\n"
print "==============================="}
{print ,"\t\t",*512,}

使用du的结果如下
代码:
[root@Linux_chenwy sam]# du
12      ./.kde/Autostart
16      ./.kde
8       ./.xemacs
4       ./sam
4       ./dir1
4       ./file6
184     .

执行:
代码:
[root@Linux_chenwy sam]# du | ./duawk.awk
name                    bytes   blocks

===============================
./.kde/Autostart                                6144    12
./.kde                          8192    16
./.xemacs                               4096    8
./sam                           2048    4
./dir1                          2048    4
./file6                         2048    4
.                               94208   184

代码:
OFS="\t";

数组
前面讲述s p l i t函数时,提到怎样使用它将元素划分进一个数组。这里还有一个例子:
代码:
[sam@Linux_chenwy sam]$ awk 'BEGIN {print split("123#456#789",myarray,"#")}'
3

实际上m y a r r a y数组为
代码:
Myarray[1]="123"
Myarray[2]="456"
Myarray[3]="789"

数组使用前,不必定义,也不必指定数组元素个数。经常使用循环来访问数组。下面是一种循环类型的基本结构:
代码:
For (element in array ) print array[element]

对于记录“ 1 2 3 # 4 5 6 # 6 7 8”,先使用s p l i t函数划分它,再使用循环打印各数组元素。操作脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat arraytest.awk
#!/bin/awk -f
#name:arraytest.awk
#prints out an array
BEGIN{
record="123#456#789";
split(record,myarray,"#")}
END{for (i in myarray) {print myarray[i]}}

要运行脚本,使用/ d e v / n u l l作为输入文件。
代码:
sam@Linux_chenwy sam]$chmod u+x arraytest.awk
[sam@Linux_chenwy sam]$ ./arraytest.awk /dev/null
123
456
789
[sam@Linux_chenwy sam]$

数组和记录
上面的例子讲述怎样通过s p l i t函数使用数组。也可以预先定义数组,并使用它与域进行比较测试,下面的例子中将使用更多的数组。
下面是从空手道数据库卸载的一部分数据,包含了学生级别及是否是成人或未成年人的信息,有两个域,分隔符为( #),文件如下:
代码:
[sam@Linux_chenwy sam]$ cat grade_student.txt
Yellow#Junior
Orange#Senior
Yellor#Junior
Purple#Junior
Brown-2#Junior
White#Senior
Orange#Senior
Red#Junior
Red#Junior
Brown-2#Senior
Yellow#Senior
Red#Junior
Blue#Senior
Green#Senior
Purple#Junior
White#Junior

脚本功能是读文件并输出下列信息。
1) 俱乐部中Ye l l o w、O r a n g e和R e d级别的人各是多少。
2 ) 俱乐部中有多少成年人和未成年人。
查看文件,也许2 0秒内就会猜出答案,但是如果记录超过6 0个又怎么办呢?这不会很容易就看出来,必须使用a w k脚本。
首先看看a w k脚本,然后做进一步讲解。

代码:
[sam@Linux_chenwy sam]$ cat belts.awk
#!/bin/awk -f
#name:belts.awk
#to call:belts.awk grade2.txt
#loops through the grade2.txt file and counts how many
#belts we have in (yellow,orange,red)
#also count how many adults and juniors we have
#
#start of BEGIN
#set FS and load the arrays with our values

#B E G I N部分设置F S为符号#,即域分隔符

BEGIN{FS="#"

#Load the belt colours we are interested in only
#因为要查找Ye l l o w、O r a n g e和R e d三个级别。
#然后在脚本中手工建立数组下标对学生做同样的操作。
#注意,脚本到此只有下标或元素,并没有给数组名本身加任何注释。

belt["Yellow"]
belt["Orange"]
belt["Red"]
#end of BEGIN
#load the student type
student["Junior"]
student["Senior"]
}

##初始化完成后, B E G I N部分结束。记住B E G I N部分并没有文件处理操作。

#loop thru array that holds the belt colours against field-1
#if we have a match,keep a running total

#现在可以处理文件了。
#首先给数组命名为c o l o r,使用循环语句测试域1级别列是否
#等于数组元素之一(Ye l l o w、O r a n g e或R e d),
#如果匹配,依照匹配元素将运行总数保存进数组。

{for (colour in belt)
{if(==colour)
belt[colour]++}}

#loop thru array that holds the student type against
#field-2 if we have a match,keep a runing total

#同样处理数组‘ S e n i o r _ o r _ j u n i o r’,
#浏览域2时匹配操作满足,运行总数存入j u n i o r或s e n i o r的匹配数组元素。

{for (senior_or_junior in student)
{if (==senior_or_junior)
student[senior_or_junior]++}}

#finished processing so print out the matches..for each array

#E N D部分打印浏览结果,对每一个数组使用循环语句并打印它。

END{for (colour in belt )print "The club has ",belt[colour],colour,"Belts"

#注意在打印语句末尾有一个\符号,用来通知a w k(或相关脚本)命令持续到下一行,
#当输入一个很长的命令,并且想分行输入时可使用这种方法。

for (senior_or_junior in student) print "The club has ",\
student[senior_or_junior],senior_or_junior,"student"}

运行脚本前记住要加入可执行权限

代码:
[sam@Linux_chenwy sam]$ chmod u+x belts.awk
[sam@Linux_chenwy sam]$ ./belts.awk grade_student.txt
The club has  3 Red Belts
The club has  2 Orange Belts
The club has  2 Yellow Belts
The club has  7 Senior student
The club has  9 Junior student

[[i] Last edited by 无奈何 on 2006-10-27 at 02:55 AM [/i]]
作者: 无奈何     时间: 2006-10-27 02:20
占一楼
作者: 无奈何     时间: 2006-10-27 02:20    标题: 通用线程:Awk 实例

转贴注:原始链接http://www-128.ibm.com/developer ... wk/awk-1/index.html

通用线程:Awk 实例,第 1部分

一种名称很奇特的优秀语言介绍

Daniel Robbins
总裁兼 CEO, Gentoo Technologies, Inc.
2000 年 12 月

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: halt          uid:7
username: operator      uid:11
username: root          uid:0
username: shutdown      uid:6
username: sync          uid:5
username: bin           uid:1
....etc.

外部脚本
将脚本作为命令行自变量传递给 awk 对于小的单行程序来说是非常简单的,而对于多行程序,它就比较复杂。您肯定想要在外部文件中撰写脚本。然后可以向 awk 传递 -f 选项,以向它提供此脚本文件:

$ awk -f myscript.awk myfile.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 命令。这两个方法都可以使用,可以选择最适合脚本其它部分的一种方法。

以下是更复杂的 awk if 语句示例。可以看到,尽管使用了复杂、嵌套的条件语句, if 语句看上去仍与相应的 C 语言 if 语句一样:

{
    if ( $1 == "foo" ) {
        if ( $2 == "foo" ) {
            print "uno"
        } else {
            print "one"
        }
    } else if ($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 "I found " x " blank lines. :)" }

在 BEGIN 块中,将整数变量 x 初始化成零。然后,awk 每次遇到空白行时,awk 将执行 x=x+1 语句,递增 x 。处理完所有行之后,执行 END 块,awk 将打印出最终摘要,指出它找到的空白行数量。

字符串化变量
awk 的优点之一就是“简单和字符串化”。我认为 awk 变量“字符串化”是因为所有 awk 变量在内部都是按字符串形式存储的。同时,awk 变量是“简单的”,因为可以对它执行数学操作,且只要变量包含有效数字字符串,awk 会自动处理字符串到数字的转换步骤。要理解我的观点,请研究以下这个示例:

x="1.01"
# We just set x to contain the *string* "1.01"
x=x+1
# We just added one to a *string*
print x
# Incidentally, these are comments :)

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 "this particular record has three fields: " $0 }

当然,也可以在条件语句中使用 NF 变量,如下:

{
    if ( NF > 2 ) {
        print $1 " " $2 ":" $3
    }
}

记录号
记录号 (NR) 是另一个方便的变量。它始终包含当前记录的编号(awk 将第一个记录算作记录号 1)。迄今为止,我们已经处理了每一行包含一个记录的输入文件。对于这些情况,NR 还会告诉您当前行号。然而,当我们在本系列以后部分中开始处理多行记录时,就不会再有这种情况,所以要注意!可以象使用 NF 变量一样使用 NR 来只打印某些输入行:

(NR < 10 ) || (NR > 100) { print "We are on record number 1-9 or 101+" }

另一个示例:

{
    #skip header
    if ( NR > 10 ) {
        print "ok, now for the real information!"
    }
}

awk 提供了适合各种用途的附加变量。我们将在以后的文章中讨论这些变量。

现在已经到了初次探索 awk 的尾声。随着本系列的开展,我将演示更高级的 awk 功能,我们将用一个真实的 awk 应用程序作为本系列的结尾。同时,如果急于学习更多知识,请参考以下列出的参考资料。

=====================================================================
通用线程:Awk 实例,第 2部分
记录、循环和数组

Daniel Robbins
总裁兼 CEO, Gentoo Technologies, Inc.
2001 年 1 月

在这篇 awk简介的续集中,Daniel Robbins 继续探索awk(一种很棒但有怪异名称的语言)。Daniel将演示如何处理多行记录、使用循环结构,以及创建并使用 awk数组。阅读完本文后,您将精通许多 awk的功能,而且可以编写您自己的功能强大的 awk 脚本。
多行记录
awk 是一种用于读取和处理结构化数据(如系统的 /etc/passwd 文件)的极佳工具。/etc/passwd 是 UNIX 用户数据库,并且是用冒号定界的文本文件,它包含许多重要信息,包括所有现有用户帐户和用户标识,以及其它信息。在我的 前一篇文章 中,我演示了 awk 如何轻松地分析这个文件。我们只须将 FS(字段分隔符)变量设置成 ":"。

正确设置了 FS 变量之后,就可以将 awk 配置成分析几乎任何类型的结构化数据,只要这些数据是每行一个记录。然而,如果要分析占据多行的记录,仅仅依靠设置 FS 是不够的。在这些情况下,我们还需要修改 RS 记录分隔符变量。RS 变量告诉 awk 当前记录什么时候结束,新记录什么时候开始。

譬如,让我们讨论一下如何完成处理“联邦证人保护计划”所涉及人员的地址列表的任务:

Jimmy the Weasel
100 Pleasant Drive
San Francisco, CA 12345

Big Tony
200 Incognito Ave.
Suburbia, WA 67890

理论上,我们希望 awk 将每 3 行看作是一个独立的记录,而不是三个独立的记录。如果 awk 将地址的第一行看作是第一个字段 ($1),街道地址看作是第二个字段 ($2),城市、州和邮政编码看作是第三个字段 $3,那么这个代码就会变得很简单。以下就是我们想要得到的代码:

BEGIN {
    FS="\n"
    RS=""
}

在上面这段代码中,将 FS 设置成 "\n" 告诉 awk 每个字段都占据一行。通过将 RS 设置成 "",还会告诉 awk 每个地址记录都由空白行分隔。一旦 awk 知道是如何格式化输入的,它就可以为我们执行所有分析工作,脚本的其余部分很简单。让我们研究一个完整的脚本,它将分析这个地址列表,并将每个记录打印在一行上,用逗号分隔每个字段。

address.awk

BEGIN {
    FS="\n"
    RS=""
}

{
    print $1 ", " $2 ", " $3

}

如果这个脚本保存为 address.awk,地址数据存储在文件 address.txt 中,可以通过输入 "awk -f address.awk address.txt" 来执行这个脚本。此代码将产生以下输出:

Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345
Big Tony, 200 Incognito Ave., Suburbia, WA 67890

OFS 和 ORS
在 address.awk 的 print 语句中,可以看到 awk 会连接(合并)一行中彼此相邻的字符串。我们使用此功能在同一行上的三个字段之间插入一个逗号和空格 (", ")。这个方法虽然有用,但比较难看。与其在字段间插入 ", " 字符串,倒不如让通过设置一个特殊 awk 变量 OFS,让 awk 完成这件事。请参考下面这个代码片断。

print "Hello", "there", "Jim!"

这行代码中的逗号并不是实际文字字符串的一部分。事实上,它们告诉 awk "Hello"、"there" 和 "Jim!" 是单独的字段,并且应该在每个字符串之间打印 OFS 变量。缺省情况下,awk 产生以下输出:

Hello there Jim!

这是缺省情况下的输出结果,OFS 被设置成 " ",单个空格。不过,我们可以方便地重新定义 OFS,这样 awk 将插入我们中意的字段分隔符。以下是原始 address.awk 程序的修订版,它使用 OFS 来输出那些中间的 ", " 字符串:

address.awk 的修订版

BEGIN {
    FS="\n"
    RS=""
    OFS=", "
}

{
    print $1, $2, $3

}

awk 还有一个特殊变量 ORS,全称是“输出记录分隔符”。通过设置缺省为换行 ("\n") 的 OFS,我们可以控制在 print 语句结尾自动打印的字符。缺省 ORS 值会使 awk 在新行中输出每个新的 print 语句。如果想使输出的间隔翻倍,可以将 ORS 设置成 "\n\n"。或者,如果想要用单个空格分隔记录(而不换行),将 ORS 设置成 ""。

将多行转换成用 tab 分隔的格式
假设我们编写了一个脚本,它将地址列表转换成每个记录一行,且用 tab 定界的格式,以便导入电子表格。使用稍加修改的 address.awk 之后,就可以清楚地看到这个程序只适合于三行的地址。如果 awk 遇到以下地址,将丢掉第四行,并且不打印该行:

Cousin Vinnie
Vinnie's Auto Shop
300 City Alley
Sosueme, OR 76543

要处理这种情况,代码最好考虑每个字段的记录数量,并依次打印每个记录。现在,代码只打印地址的前三个字段。以下就是我们想要的一些代码:

适合具有任意多字段的地址的 address.awk 版本

BEGIN {
    FS="\n"
    RS=""
    ORS=""
}

{
        x=1
        while ( x<NF ) {
                print $x "\t"
                x++
        }
        print $NF "\n"
}

首先,将字段分隔符 FS 设置成 "\n",将记录分隔符 RS 设置成 "",这样 awk 可以象以前一样正确分析多行地址。然后,将输出记录分隔符 ORS 设置成 "",它将使 print 语句在每个调用结尾 不 输出新行。这意味着如果希望任何文本从新的一行开始,那么需要明确写入 print "\n" 。

在主代码块中,创建了一个变量 x 来存储正在处理的当前字段的编号。起初,它被设置成 1。然后,我们使用 while 循环(一种 awk 循环结构,等同于 C 语言中的 while 循环),对于所有记录(最后一个记录除外)重复打印记录和 tab 字符。最后,打印最后一个记录和换行;此外,由于将 ORS 设置成 "",print 将不输出换行。程序输出如下,这正是我们所期望的:

我们想要的输出。不算漂亮,但用 tab 定界,以便于导入电子表格

Jimmy the Weasel        100 Pleasant Drive      San Francisco, CA 12345
Big Tony        200 Incognito Ave.      Suburbia, WA 67890
Cousin Vinnie   Vinnie's Auto Shop      300 City Alley  Sosueme, OR 76543

循环结构
我们已经看到了 awk 的 while 循环结构,它等同于相应的 C 语言 while 循环。awk 还有 "do...while" 循环,它在代码块结尾处对条件求值,而不象标准 while 循环那样在开始处求值。它类似于其它语言中的 "repeat...until" 循环。以下是一个示例:

do...while 示例

{
    count=1
    do {
        print "I get printed at least once no matter what"
    } while ( count != 1 )
}

与一般的 while 循环不同,由于在代码块之后对条件求值,"do...while" 循环永远都至少执行一次。换句话说,当第一次遇到普通 while 循环时,如果条件为假,将永远不执行该循环。

for 循环
awk 允许创建 for 循环,它就象 while 循环,也等同于 C 语言的 for 循环:

for ( initial assignment; comparison; increment ) {
    code block
}

以下是一个简短示例:

for ( x = 1; x <= 4; x++ ) {
    print "iteration",x
}

此段代码将打印:

iteration 1
iteration 2
iteration 3
iteration 4

break 和 continue
此外,如同 C 语言一样,awk 提供了 break 和 continue 语句。使用这些语句可以更好地控制 awk 的循环结构。以下是迫切需要 break 语句的代码片断:

while 死循环

while (1) {
    print "forever and ever..."
}

while 死循环 1 永远代表是真,这个 while 循环将永远运行下去。以下是一个只执行十次的循环:

break 语句示例

x=1
while(1) {
    print "iteration",x
    if ( x == 10 ) {
        break
    }
    x++
}

这里,break 语句用于“逃出”最深层的循环。"break" 使循环立即终止,并继续执行循环代码块后面的语句。

continue 语句补充了 break,其作用如下:

x=1
while (1) {
    if ( x == 4 ) {
        x++
        continue
    }
    print "iteration",x
    if ( x > 20 ) {
        break
    }
    x++
}

这段代码打印 "iteration 1" 到 "iteration 21","iteration 4" 除外。如果迭代等于 4,则增加 x 并调用 continue 语句,该语句立即使 awk 开始执行下一个循环迭代,而不执行代码块的其余部分。如同 break 一样,continue 语句适合各种 awk 迭代循环。在 for 循环主体中使用时,continue 将使循环控制变量自动增加。以下是一个等价循环:

for ( x=1; x<=21; x++ ) {
    if ( x == 4 ) {
        continue
    }
    print "iteration",x
}

在 while 循环中时,在调用 continue 之前没有必要增加 x ,因为 for 循环会自动增加 x 。

数组
如果您知道 awk 可以使用数组,您一定会感到高兴。然而,在 awk 中,数组下标通常从 1 开始,而不是 0:

myarray[1]="jim"
myarray[2]=456

awk 遇到第一个赋值语句时,它将创建 myarray ,并将元素 myarray[1] 设置成 "jim"。执行了第二个赋值语句后,数组就有两个元素了。

数组迭代
定义之后,awk 有一个便利的机制来迭代数组元素,如下所示:

for ( x in myarray ) {
    print myarray[x]
}

这段代码将打印数组 myarray 中的每一个元素。当对于 for 使用这种特殊的 "in" 形式时,awk 将 myarray 的每个现有下标依次赋值给 x (循环控制变量),每次赋值以后都循环一次循环代码。虽然这是一个非常方便的 awk 功能,但它有一个缺点 -- 当 awk 在数组下标之间轮转时,它不会依照任何特定的顺序。那就意味着我们不能知道以上代码的输出是:

jim
456

还是

456
jim

套用 Forrest Gump 的话来说,迭代数组内容就像一盒巧克力 -- 您永远不知道将会得到什么。因此有必要使 awk 数组“字符串化”,我们现在就来研究这个问题。

数组下标字符串化
在我的 前一篇文章 中,我演示了 awk 实际上以字符串格式来存储数字值。虽然 awk 要执行必要的转换来完成这项工作,但它却可以使用某些看起来很奇怪的代码:

a="1"
b="2"
c=a+b+3

执行了这段代码后, c 等于 6 。由于 awk 是“字符串化”的,添加字符串 "1" 和 "2" 在功能上并不比添加数字 1 和 2 难。这两种情况下,awk 都可以成功执行运算。awk 的“字符串化”性质非常可爱 -- 您可能想要知道如果使用数组的字符串下标会发生什么情况。例如,使用以下代码:

myarr["1"]="Mr. Whipple"
print myarr["1"]

可以预料,这段代码将打印 "Mr. Whipple"。但如果去掉第二个 "1" 下标中的引号,情况又会怎样呢?

myarr["1"]="Mr. Whipple"
print myarr[1]

猜想这个代码片断的结果比较难。awk 将 myarr["1"] 和 myarr[1] 看作数组的两个独立元素,还是它们是指同一个元素?答案是它们指的是同一个元素,awk 将打印 "Mr. Whipple",如同第一个代码片断一样。虽然看上去可能有点怪,但 awk 在幕后却一直使用数组的字符串下标!

了解了这个奇怪的真相之后,我们中的一些人可能想要执行类似于以下的古怪代码:

myarr["name"]="Mr. Whipple"
print myarr["name"]

这段代码不仅不会产生错误,而且它的功能与前面的示例完全相同,也将打印 "Mr. Whipple"!可以看到,awk 并没有限制我们使用纯整数下标;如果我们愿意,可以使用字符串下标,而且不会产生任何问题。只要我们使用非整数数组下标,如 myarr["name"] ,那么我们就在使用 关联数组 。从技术上讲,如果我们使用字符串下标,awk 的后台操作并没有什么不同(因为即便使用“整数”下标,awk 还是会将它看作是字符串)。但是,应该将它们称作 关联数组 -- 它听起来很酷,而且会给您的上司留下印象。字符串化下标是我们的小秘密。;)

数组工具
谈到数组时,awk 给予我们许多灵活性。可以使用字符串下标,而且不需要连续的数字序列下标(例如,可以定义 myarr[1] 和 myarr[1000] ,但不定义其它所有元素)。虽然这些都很有用,但在某些情况下,会产生混淆。幸好,awk 提供了一些实用功能有助于使数组变得更易于管理。

首先,可以删除数组元素。如果想要删除数组 fooarray 的元素 1 ,输入:

delete fooarray[1]

而且,如果想要查看是否存在某个特定数组元素,可以使用特殊的 "in" 布尔运算符,如下所示:

if ( 1 in fooarray ) {
    print "Ayep!  It's there."
} else {
    print "Nope!  Can't find it."
}

=====================================================================
通用线程:Awk 实例,第 3部分
在这篇 awk 系列的总结中,Daniel 向您介绍 awk 重要的字符串函数,以及演示了如何从头开始编写完整的支票簿结算程序。在这个过程中,您将学习如何编写自己的函数,并使用 awk 的多维数组。学完本文之后,您将掌握更多 awk 经验,可以让您创建功能更强大的脚本。
格式化输出
虽然大多数情况下 awk 的 print 语句可以完成任务,但有时我们还需要更多。在那些情况下,awk 提供了两个我们熟知的老朋友 printf() 和 sprintf()。是的,如同其它许多 awk 部件一样,这些函数等同于相应的 C 语言函数。printf() 会将格式化字符串打印到 stdout,而 sprintf() 则返回可以赋值给变量的格式化字符串。如果不熟悉 printf() 和 sprintf(),介绍 C 语言的文章可以让您迅速了解这两个基本打印函数。在 Linux 系统上,可以输入 "man 3 printf" 来查看 printf() 帮助页面。

以下是一些 awk sprintf() 和 printf() 的样本代码。可以看到,它们几乎与 C 语言完全相同。

x=1
b="foo"
printf("%s got a %d on the last test\n","Jim",83)
myout=("%s-%d",b,x)
print myout

此代码将打印:

Jim got a 83 on the last test
foo-1

字符串函数
awk 有许多字符串函数,这是件好事。在 awk 中,确实需要字符串函数,因为不能象在其它语言(如 C、C++ 和 Python)中那样将字符串看作是字符数组。例如,如果执行以下代码:

mystring="How are you doing today?"
print mystring[3]

将会接收到一个错误,如下所示:

awk: string.gawk:59: fatal: attempt to use scalar as array

噢,好吧。虽然不象 Python 的序列类型那样方便,但 awk 的字符串函数还是可以完成任务。让我们来看一下。

首先,有一个基本 length() 函数,它返回字符串的长度。以下是它的使用方法:

print length(mystring)

此代码将打印值:

24

好,继续。下一个字符串函数叫作 index,它将返回子字符串在另一个字符串中出现的位置,如果没有找到该字符串则返回 0。使用 mystring,可以按以下方法调用它:

print index(mystring,"you")

awk 会打印:

9

让我们继续讨论另外两个简单的函数,tolower() 和 toupper()。与您猜想的一样,这两个函数将返回字符串并且将所有字符分别转换成小写或大写。请注意,tolower() 和 toupper() 返回新的字符串,不会修改原来的字符串。这段代码:

print tolower(mystring)
print toupper(mystring)
print mystring

……将产生以下输出:

how are you doing today?
HOW ARE YOU DOING TODAY?
How are you doing today?

到现在为止一切不错,但我们究竟如何从字符串中选择子串,甚至单个字符?那就是使用 substr() 的原因。以下是 substr() 的调用方法:

mysub=substr(mystring,startpos,maxlen)

mystring 应该是要从中抽取子串的字符串变量或文字字符串。startpos 应该设置成起始字符位置,maxlen 应该包含要抽取的字符串的最大长度。请注意,我说的是 最大长度 ;如果 length(mystring) 比 startpos+maxlen 短,那么得到的结果就会被截断。substr() 不会修改原始字符串,而是返回子串。以下是一个示例:

print substr(mystring,9,3)

awk 将打印:

you

如果您通常用于编程的语言使用数组下标访问部分字符串(以及不使用这种语言的人),请记住 substr() 是 awk 代替方法。需要使用它来抽取单个字符和子串;因为 awk 是基于字符串的语言,所以会经常用到它。

现在,我们讨论一些更耐人寻味的函数,首先是 match()。match() 与 index() 非常相似,它与 index() 的区别在于它并不搜索子串,它搜索的是规则表达式。match() 函数将返回匹配的起始位置,如果没有找到匹配,则返回 0。此外,match() 还将设置两个变量,叫作 RSTART 和 RLENGTH。RSTART 包含返回值(第一个匹配的位置),RLENGTH 指定它占据的字符跨度(如果没有找到匹配,则返回 -1)。通过使用 RSTART、RLENGTH、substr() 和一个小循环,可以轻松地迭代字符串中的每个匹配。以下是一个 match() 调用示例:

print match(mystring,/you/), RSTART, RLENGTH

awk 将打印:

9 9 3

字符串替换
现在,我们将研究两个字符串替换函数,sub() 和 gsub()。这些函数与目前已经讨论过的函数略有不同,因为它们 确实修改原始字符串 。以下是一个模板,显示了如何调用 sub():

sub(regexp,replstring,mystring)

调用 sub() 时,它将在 mystring 中匹配 regexp 的第一个字符序列,并且用 replstring 替换该序列。sub() 和 gsub() 用相同的自变量;唯一的区别是 sub() 将替换第一个 regexp 匹配(如果有的话),gsub() 将执行全局替换,换出字符串中的所有匹配。以下是一个 sub() 和 gsub() 调用示例:

sub(/o/,"O",mystring)
print mystring
mystring="How are you doing today?"
gsub(/o/,"O",mystring)
print mystring

必须将 mystring 复位成其初始值,因为第一个 sub() 调用直接修改了 mystring。在执行时,此代码将使 awk 输出:

HOw are you doing today?
HOw are yOu dOing tOday?

当然,也可以是更复杂的规则表达式。我把测试一些复杂规则表达式的任务留给您来完成。

通过介绍函数 split(),我们来汇总一下已讨论过的函数。split() 的任务是“切开”字符串,并将各部分放到使用整数下标的数组中。以下是一个 split() 调用示例:

numelements=split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",mymonths,",")

调用 split() 时,第一个自变量包含要切开文字字符串或字符串变量。在第二个自变量中,应该指定 split() 将填入片段部分的数组名称。在第三个元素中,指定用于切开字符串的分隔符。split() 返回时,它将返回分割的字符串元素的数量。split() 将每一个片段赋值给下标从 1 开始的数组,因此以下代码:

print mymonths[1],mymonths[numelements]

……将打印:

Jan Dec

特殊字符串形式
简短注释 -- 调用 length()、sub() 或 gsub() 时,可以去掉最后一个自变量,这样 awk 将对 $0(整个当前行)应用函数调用。要打印文件中每一行的长度,使用以下 awk 脚本:

{
    print length()
}

财务上的趣事
几星期前,我决定用 awk 编写自己的支票簿结算程序。我决定使用简单的 tab 定界文本文件,以便于输入最近的存款和提款记录。其思路是将这个数据交给 awk 脚本,该脚本会自动合计所有金额,并告诉我余额。以下是我决定如何将所有交易记录到 "ASCII checkbook" 中:

23 Aug 2000 food    -   -   Y   Jimmy's Buffet      30.25

此文件中的每个字段都由一个或多个 tab 分隔。在日期(字段 1,$1)之后,有两个字段叫做“费用分类帐”和“收入分类帐”。以上面这行为例,输入费用时,我在费用字段中放入四个字母的别名,在收入字段中放入 "-"(空白项)。这表示这一特定项是“食品费用”。:) 以下是存款的示例:

23 Aug 2000 -   inco    -   Y   Boss Man        2001.00

在这个实例中,我在费用分类帐中放入 "-"(空白),在收入分类帐中放入 "inco"。"inco" 是一般(薪水之类)收入的别名。使用分类帐别名让我可以按类别生成收入和费用的明细分类帐。至于记录的其余部分,其它所有字段都是不需加以说明的。“是否付清?”字段("Y" 或 "N")记录了交易是否已过帐到我的帐户;除此之外,还有一个交易描述,和一个正的美元金额。

用于计算当前余额的算法不太难。awk 只需要依次读取每一行。如果列出了费用分类帐,但没有收入分类帐(为 "-"),那么这一项就是借方。如果列出了收入分类帐,但没有费用分类帐(为 "-"),那么这一项就是贷方。而且,如果同时列出了费用和收入分类帐,那么这个金额就是“分类帐转帐”;即,从费用分类帐减去美元金额,并将此金额添加到收入分类帐。此外,所有这些分类帐都是虚拟的,但对于跟踪收入和支出以及预算却非常有用。

代码
现在该研究代码了。我们将从第一行(BEGIN 块和函数定义)开始:

balance,第 1 部分
#!/usr/bin/env awk -f
BEGIN {
    FS="\t+"
    months="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"
}

function monthdigit(mymonth) {
    return (index(months,mymonth)+3)/4
}

首先执行 "chmod +x myscript" 命令,那么将第一行 "#!..." 添加到任何 awk 脚本将使它可以直接从 shell 中执行。其余行定义了 BEGIN 块,在 awk 开始处理支票簿文件之前将执行这个代码块。我们将 FS(字段分隔符)设置成 "\t+",它会告诉 awk 字段由一个或多个 tab 分隔。另外,我们定义了字符串 months,下面将出现的 monthdigit() 函数将使用它。

最后三行显示了如何定义自己的 awk 。格式很简单 -- 输入 "function",再输入名称,然后在括号中输入由逗号分隔的参数。在此之后,"{ }" 代码块包含了您希望这个函数执行的代码。所有函数都可以访问全局变量(如 months 变量)。另外,awk 提供了 "return" 语句,它允许函数返回一个值,并执行类似于 C 和其它语言中 "return" 的操作。这个特定函数将以 3 个字母字符串格式表示的月份名称转换成等价的数值。例如,以下代码:

print monthdigit("Mar")

……将打印:

3

现在,让我们讨论其它一些函数。

财务函数
以下是其它三个执行簿记的函数。我们即将见到的主代码块将调用这些函数之一,按顺序处理支票簿文件的每一行,从而将相应交易记录到 awk 数组中。有三种基本交易,贷方 (doincome)、借方 (doexpense) 和转帐 (dotransfer)。您会发现这三个函数全都接受一个自变量,叫作 mybalance。mybalance 是二维数组的一个占位符,我们将它作为自变量进行传递。目前,我们还没有处理过二维数组;但是,在下面可以看到,语法非常简单。只须用逗号分隔每一维就行了。

我们将按以下方式将信息记录到 "mybalance" 中。数组的第一维从 0 到 12,用于指定月份,0 代表全年。第二维是四个字母的分类帐,如 "food" 或 "inco";这是我们处理的真实分类帐。因此,要查找全年食品分类帐的余额,应查看 mybalance[0,"food"]。要查找 6 月的收入,应查看 mybalance[6,"inco"]。

balance,第 2 部分
function doincome(mybalance) {
    mybalance[curmonth,$3] += amount
    mybalance[0,$3] += amount
}

function doexpense(mybalance) {
    mybalance[curmonth,$2] -= amount
    mybalance[0,$2] -= amount
}

function dotransfer(mybalance) {
    mybalance[0,$2] -= amount
    mybalance[curmonth,$2] -= amount
    mybalance[0,$3] += amount
    mybalance[curmonth,$3] += amount
}

调用 doincome() 或任何其它函数时,我们将交易记录到两个位置 -- mybalance[0,category] 和 mybalance[curmonth, category],它们分别表示全年的分类帐余额和当月的分类帐余额。这让我们稍后可以轻松地生成年度或月度收入/支出明细分类帐。

如果研究这些函数,将发现在我的引用中传递了 mybalance 引用的数组。另外,我们还引用了几个全局变量:curmonth,它保存了当前记录所属的月份的数值,$2(费用分类帐),$3(收入分类帐)和金额($7,美元金额)。调用 doincome() 和其它函数时,已经为要处理的当前记录(行)正确设置了所有这些变量。

主块
以下是主代码块,它包含了分析每一行输入数据的代码。请记住,由于正确设置了 FS,可以用 $ 1 引用第一个字段,用 $2 引用第二个字段,依次类推。调用 doincome() 和其它函数时,这些函数可以从函数内部访问 curmonth、$2、$3 和金额的当前值。请先研究代码,在代码之后可以见到我的说明。

balance,第 3 部分
{
    curmonth=monthdigit(substr($1,4,3))
    amount=$7

    #record all the categories encountered
    if ( $2 != "-" )
        globcat[$2]="yes"
    if ( $3 != "-" )
        globcat[$3]="yes"

    #tally up the transaction properly
    if ( $2 == "-" ) {
        if ( $3 == "-" ) {
            print "Error: inc and exp fields are both blank!"
            exit 1
        } else {
            #this is income
            doincome(balance)
            if ( $5 == "Y" )
                doincome(balance2)
        }
    } else if ( $3 == "-" ) {
        #this is an expense
        doexpense(balance)
        if ( $5 == "Y" )
            doexpense(balance2)
    } else {
        #this is a transfer
        dotransfer(balance)
        if ( $5 == "Y" )
            dotransfer(balance2)
    }
}

在主块中,前两行将 curmonth 设置成 1 到 12 之间的整数,并将金额设置成字段 7(使代码易于理解)。然后,是四行有趣的代码,它们将值写到数组 globcat 中。globcat,或称作全局分类帐数组,用于记录在文件中遇到的所有分类帐 -- "inco"、"misc"、"food"、"util" 等。例如,如果 $2 == "inco",则将 globcat["inco"] 设置成 "yes"。稍后,我们可以使用简单的 "for (x in globcat)" 循环来迭代分类帐列表。

在接着的大约二十行中,我们分析字段 $2 和 $3,并适当记录交易。如果 $2=="-" 且 $3!="-",表示我们有收入,因此调用 doincome()。如果是相反的情况,则调用 doexpense();如果 $2 和 $3 都包含分类帐,则调用 dotransfer()。每次我们都将 "balance" 数组传递给这些函数,从而在这些函数中记录适当的数据。

您还会发现几行代码说“if ( $5 == "Y" ),那么将同一个交易记录到 balance2 中”。我们在这里究竟做了些什么?您将回忆起 $5 包含 "Y" 或 "N",并记录交易是否已经过帐到帐户。由于仅当过帐了交易时我们才将交易记录到 balance2,因此 balance2 包含了真实的帐户余额,而 "balance" 包含了所有交易,不管是否已经过帐。可以使用 balance2 来验证数据项(因为它应该与当前银行帐户余额匹配),可以使用 "balance" 来确保没有透支帐户(因为它会考虑您开出的尚未兑现的所有支票)。

生成报表
主块重复处理了每一行记录之后,现在我们有了关于比较全面的、按分类帐和按月份划分的借方和贷方记录。现在,在这种情况下最合适的做法是只须定义生成报表的 END 块:

balance,第 4 部分
END {
    bal=0
    bal2=0
    for (x in globcat) {
        bal=bal+balance[0,x]
        bal2=bal2+balance2[0,x]
    }
    printf("Your available funds: %10.2f\n", bal)
    printf("Your account balance: %10.2f\n", bal2)
}

这个报表将打印出汇总,如下所示:

Your available funds:1174.22
Your account balance:2399.33

在 END 块中,我们使用 "for (x in globcat)" 结构来迭代每一个分类帐,根据记录在案的交易结算主要余额。实际上,我们结算两个余额,一个是可用资金,另一个是帐户余额。要执行程序并处理您在文件 "mycheckbook.txt" 中输入的财务数据,将以上所有代码放入文本文件 "balance",执行 "chmod +x balance",然后输入 "./balance mycheckbook.txt"。然后 balance 脚本将合计所有交易,打印出两行余额汇总。

升级
我使用这个程序的更高级版本来管理我的个人和企业财务。我的版本(由于篇幅限制不能在此涵盖)会打印出收入和费用的月度明细分类帐,包括年度总合、净收入和其它许多内容。它甚至以 HTML 格式输出数据,因此我可以在 Web 浏览器中查看它。:) 如果您认为这个程序有用,我建议您将这些特性添加到这个脚本中。不必将它配置成要 记录 任何附加信息;所需的全部信息已经在 balance 和 balance2 里面了。只要升级 END 块就万事具备了!

我希望您喜欢本系列。有关 awk 的详细信息,请参考以下列出的参考资料。


参考资料
您可以参阅本文在 developerWorks 全球站点上的 英文原文.
请阅读 Daniel 在 developerWorks 上发表的 awk 系列中的前几篇文章:awk 实例, 第 1 部分第 2 部分
如果想看好的老式书籍,O'Reilly 的 sed & awk, 2ndEdition是极佳选择。
请参考 comp.lang.awkFAQ 。它还包含许多附加 awk 链接。
Patrick Hartigan 的 awk tutorial 还包括了实用的 awk 脚本。
Thompson's TAWKCompiler 将 awk 脚本编译成快速二进制可执行文件。可用版本有 Windows 版、OS/2 版、DOS 版和 UNIX 版。
The GNUAwk User's Guide可用于在线参考。

[ Last edited by 无奈何 on 2006-10-27 at 03:09 AM ]
作者: 无奈何     时间: 2006-10-27 02:20    标题: awk - 模式扫描与处理语言

转贴注:原始链接http://blog.chinaunix.net/u/13392/showart.php?id=134410

awk - 模式扫描与处理语言(第二版)
awk-模式扫描与处理语言(Aho,Kernighan,Weinberger著,中文翻译)

Alfred V. Aho
Brian W. Kernighan
Peter J. Weinberger
Bell Laboratories
Murray Hill, New Jersey 07974

翻译:寒蝉退士

译者声明:译者对译文不做任何担保,译者对译文不拥有任何权利并且不负担任何责任和义务。
原文:http://cm.bell-labs.com/7thEdMan/vol2/awk

摘要

awk 是一门编程语言,它的基本操作是在一组文件上查找模式,并在包含这些模式实例的那些行或字段上进行指定的动作。awk 使得特定数据的选择和变换操作更易于表达;例如,awk 程序

length > 72
打印长度超过 72 个字符的所有输入行;程序

NF % 2 == 0
打印有偶数个字段的所有行;而程序

{ $1 = log($1); print }
把每行的第一个字段替代为它的对数。

awk 模式可以包括正则表达式和在字符串、数值、字段、变量、数组元素上的关系算符的任意的布尔组合。动作可以包括同在模式中一样的模式匹配构造,还有算术和字符串表达式与赋值,if-else、while、for 语句,和多个输出流。

本报告包含用户指南,awk 设计与实现的讨论,和一些计时统计。

September 1, 1978

--------------------------------------------------------------------------------

1. 介绍
1.1. 用法
1.2. 程序结构
1.3. 记录和字段
1.4. 打印
2. 模式
2.1. BEGIN 和 END
2.2. 正则表达式
2.3. 关系表达式
2.4. 模式的组合
2.5. 模式范围
3. 动作
3.1. 内置函数
3.2. 变量、表达式和赋值
3.3. 字段变量
3.4. 字符串连接
3.5. 数组
3.6. 控制流语句
4. 设计
5. 实现
引用

--------------------------------------------------------------------------------

1. 介绍
awk 是设计用来使很多常见的信息检索和文本操作任务易于陈述和进行的一门编程语言。

awk 的基本操作是依次扫描一组输入文件,查找匹配与用户已经指定的模式集合中的任何一个模式相匹配的行。对于每个模式,都可以指定一个动作;这个动作将在匹配这个模式每一行上进行。

尽管读者熟悉的 UNIX ? 程序 grep 也认可这种方式,在 awk 中的模式可能比 grep 中的模式更加一般性,而且允许的动作比只是打印匹配的行更加复杂。例如,awk 程序

{print $3, $2}

依次打印一个表格的第三和第二列。程序

$2 ~ /A|B|C/

打印在第二列是 A、B 或 C 的所有输入行。程序

$1 != prev { print; prev = $1 }
打印第一个字段不同于前面的第一个字段的所有的行。

1.1. 用法
命令

awk  program  [files]

在指名的一组文件上,或标准输入上、如果没有指定文件的话,执行字符串 program 中的 awk 命令。语句也可以放置到一个文件 pfile 中,并用如下命令执行。

awk  -f pfile  [files]

1.2. 程序结构
awk 程序是如下形式的语句序列:

模式 { 动作 }
模式 { 动作 }
...

输入的每行都要依次针对每个模式做匹配。对于每个匹配的模式,执行相关的动作。在所有模式都已经测试过了的时候,取回下一行并从头开始做匹配。

模式或动作二者都可以但不能同时省略。如果一个模式没有动作,简单的把匹配的行复制到输出。(所以匹配多个模式的行可能被打印多次)。如果一个动作没有模式,则这个动作在所有输入上进行。不匹配模式的行被忽略。

因为模式和动作都是可选的,动作必须被包围在花括号中来区别于模式。

1.3. 记录和字段
awk 输入被分解成了终止于记录分隔符的“记录”。缺省的记录分隔符是换行,所以缺省的 awk 一次处理它的输入中的一行。当前记录的数可在命名为 NR 的变量中得到。

每个输入记录被当作分解成了“字段”。字段通常用空白也就是空格或 tab 来分隔,但是输入字段分隔符是可以变更的,这在后面会有所描述。字段被引用为 $1、$2 ,以此类推。这里的 $1 是第一个字段,而 $0 是整个输入记录自身。字段可以被赋值。在当前记录中字段的数目可以在命名为 NF 的变量中得到。

变量 FS 和 RS 分别指定输入字段和记录分隔符;它们可以在任何时候被改变为任何的单一字符。也可以使用可选的命令行参数 ?Fc 来设置 FS 为字符 c。

如果记录分隔符为空,把空输入行作为记录分隔符,并把空格、tab 和换行作为字段分隔符处理。

变量 FILENAME 包含当前输入文件的名字。

1.4. 打印
一个动作可以没有模式,在这种情况下动作在所有行上执行。最简单的动作是打印某些或所有的记录;这可以通过 awk 命令 print 来完成。awk 程序

{ print }
打印每个记录,也就是把输入完好的复制到输出。更有用的是打印来自每个记录的一个字段或某些字段。例如

print $2, $1
按逆序打印前两个字段。在 print 语句中用逗号分隔的项,在输出的时候会用当前输出字段分隔符分隔开。没有用逗号分隔的项会串联起来,所以

print $1 $2
把第一个和第二个字段合在一起。

可以使用预定义的变量 NF 和 NR;例如

   { print NR, NF, $0 }
打印出前导了记录数和字段数的每个记录。

输出可以被转向到多个文件中;程序

   { print $1 >"foo1"; print $2 >"foo2" }
写第一个字段 $1 到文件 foo1 中,写第二个字段到文件 foo2 中。还可以使用 >> 符号:

   print $1 >>"foo"
添加输出到文件 foo。(在每种情况下,输出文件都在必要时建立)。文件名可以是一个变量或字段,同常量一样;例如

   print $1 >$2
使用字段 2 的内容作为文件名字。

自然的,有对输出文件数目的限制,目前是 10 个。

类似的,输出可以用管道导入到(只在 UNIX 上的)其他进程;例如,

   print |  "mail bwk"
把输入邮递给 bwk。

可以使用变量 OFS 和 ORS 来改变当前输出字段分隔符和输出记录分隔符。输出记录分隔符被添加到 print 语句的输出后面。

awk 还提供 printf 语句用于输出格式化:

   printf format expr, expr, ...
依据在 format 中的规定格式化在列表中的表达式并打印它们。例如,

   printf "%8.2f  %10ld\n", $1, $2

打印 $1 为 8 位宽的小数点后有两位的浮点数,打印 $2 为 10 位长的长十进制数,并跟随着一个换行。不自动生成输出分隔符;你必须自己增加它们,如这个例子那样。这个版本的 printf 同于 C 语言所使用的。

--------------------------------------------------------------------------------

2. 模式
在动作之前的模式充当决定一个动作是否执行的选择者。有多种多样的表达式可以被用做模式: 正则表达式,算术关系表达式,字符串值的表达式,和它们的任意的布尔组合。

2.1. BEGIN 和 END
特殊模式 BEGIN 匹配输入的开始,在第一个记录被读取之前。模式 END 匹配输入的结束,在最后一个记录已经被处理之后。BEGIN 和 END 从而提供了在处理之前和之后获得控制的方式,用来做初始化和总结。

作为一个例子,可以如下这样把字段分隔符设置为冒号

BEGIN { FS = ":" }
... 余下的程序 ...
或如下这样输出输入行的计数

END  { print NR }

如果 BEGIN 出现,它必须是第一模式;END 必须是最后一个模式,如果用到了的话。

2.2. 正则表达式
最简单的正则表达式是包围在斜杠内的文字的字符串,如

/smith/
这实际上是个完整的 awk 程序,它将打印包含名字“smith”的任何出现的所有行。如果一行包含“smith”作为一个大单词的一部分,它也会被打印,比如

blacksmithing
awk 正则表达式包括在 UNIX 文本编辑器 ed 和 grep 中能找到的正则表达式形式(没有后引用)。此外同 lex 一样,awk 允许采用圆括号用做组合,| 用做选择,+ 用做“一或多个”,? 用于“零或一个”。字符类可以简写: [a?zA?Z0?9] 是所有字母和数字的集合。作为例子,awk 程序

/[Aa]ho|[Ww]einberger|[Kk]ernighan/
将打印包含名字“Aho”、“Weinberger”或“Kernighan”中任何一个、不论首字母是否大写的所有行。

(带有上述扩展的)正则表达式必须包围在斜杠中,同 ed 和 sed 一样。在正则表达式内,空白和正则表达式元字符是有意义的。要去掉某个正则表达式字符的特殊意义,可前导一个反斜杠。一个例子模式

/\/.*\//
它匹配包围在斜杠内的任何字符串。

你还可以通过算符 ~ 和 !~ 指定任何字段或变量匹配(或不匹配)一个正则表达式。程序

$1 ~ /[jJ]ohn/
打印第一个字段匹配“john”或“John”的所有行。注意它还会匹配“Johnson” 和“St. Johnsbury”等等。要精确的限制它为 [jJ]ohn,使用

$1 ~ /^[jJ]ohn$/
这个脱字符号 ^ 指称一行或一个字段的开始处;美元号 $ 指称结束处。

2.3. 关系表达式
awk 模式可以是涉及常用的关系算符 <、<=、==、!=、>=、> 的关系表达式。 例子

$2 > $1 + 100
它选择第二个字段至少比第一个字段大 100 的行。类似的

NF % 2 == 0
打印有偶数个字段的行。

在关系测试中,如果操作数(operand)都不是数值,则做字符串比较;否则做数值比较。所以

$1 >= "s"
选择开始于 s、t、u 等字符的行。在缺乏任何其他信息的情况下,字段被当作字符串,所以程序

$1 > $2
将进行字符串比较。

2.4. 模式的组合
模式可以是模式的使用算符 ||(或)、&&(与)和 !(非)的任意布尔组合。例如

$1 >= "s" && $1 < "t" && $1 != "smith"
选择第一字段开始于“s”而不是“smith”的行。&& 和 || 保证它们的操作数会被从左至右的求值;在确定了真或假之后求值立即停止。

2.5. 模式范围
选择一个动作的“模式”还可以由用逗号分隔的两个模式组成,比如

pat1,{ ... } pat2
在这种情况下,这个动作在 pat1 的一个出现和 pat2 的下一个出现之间(包含它们)的每个行上进行。例如,

/start/, /stop/
打印在 start 和 stop 之间的所有行。而

NR == 100, NR == 200 { ... }
在输入的从 100 到 200 的行上进行这个动作。

--------------------------------------------------------------------------------

3. 动作
awk 动作是用换行或分号终止的动作语句的序列。这些动作语句可以被用来做各种各样的簿记和字符串操纵任务。

3.1. 内置函数
awk 提供了一个“长度”函数来计算字符串的长度。下面这个程序打印每个记录,每个都前导它的长度:

{print length, $0}
length 自身是个“伪变量”,它生成当前记录的长度;length(参数)生成它的参数的长度,下面的程序等价于上个程序

{print length($0), $0}
参数可以是任何表达式。

awk 还提供算术函数 sqrt、log、exp 和 int,分别得到它们参数的平方根、自然对数、指数和整数部分。

某个内置函数的名字,不带有参数或圆括号,表示这些函数在整个记录上的值。程序

length < 10 ||  length > 20
打印长度小于 10 或大于 20 的行。

函数 substr(s, m, n) 生成 s 的开始于位置 m(起始于 1)的最多 n 个字符长的子串。如果省略了 n,子串到达 s 的结束处。函数 index(s1, s2) 返回字符串 s2 在 s1 出现的位置,如果未出现则为零。

函数 sprintf(f, e1, e2, ...) 在 f 指定的 printf 格式中生成表达式 e1、e2 等的值。所以例子

x = sprintf("%8.2f %10ld", $1, $2)

设置 x 为格式化 $1 和 $2 的值所生成的字符串。

3.2. 变量、表达式和赋值
awk 变量依据上下文而被接纳为数值(浮点数)或字符串值。例如

x = 1
x 明显的是个数,而

x = "smith"
明显的是个字符串。在上下文需要的时候,把字符串转换为数或反之。例如

x = "3" + "4"
把 7 赋值给 x。在数值上下文中,不能被解释为数的字符串一般会有为零的数值,但是依靠这种行为是愚蠢的。

缺省的,(不是内置的)变量被初始化为空字符串,它有为零的数值;这消除了大多数对 BEGIN 段落的需要。例如,前两个字段的总和可以用下列程序计算

{ s1 += $1; s2 += $2 }
END { print s1, s2 }
算术在内部以浮点数的方式计算。算术算符有 +、-、*、/、%(模)。C 语言的增加 ++ 和减少 ?? 算符也可用,还有赋值算符 +=、-=、*=、/=、%=。这些算符都可以用于表达式中。

3.3. 字段变量
awk 中的字段在本质上享有变量的所有性质 — 他们可以用在算术或字符串运算/操作中,并可以被赋值。所以你可以把第一个字段替代为一个序号,比如:

{ $1 = NR; print }
或累计前两个字段到第三个字段中,比如:

{ $1 = $2 + $3; print $0 }
或把一个字符串赋值到一个字段:

{ if ($3 > 1000)
        $3 = "too big"
  print
}
它把第三个字段替代为“too big”,在它很长的时候,并在这种情况下,打印这个记录。

字段引用可以是数值表达式,比如

{ print $i, $(i+1), $(i+n) }
一个字段被认为是数值还是字符串依赖于上下文;在有歧义的情况下比如

if ($1 == $2) ...
字段被当作字符串。

每个输入行都在需要的时候被自动分解到字段。还可以把任意变量或字段分解到字段:

n = split(s, array, sep)
把字符串 s 分解到 array[1], ..., array[n]。返回找到的元素数目。如果提供了 sep 参数,则把它用做字段分隔符;否则使用 FS 作为分隔符。

3.4. 字符串连接
字符串可以被串接。例如

length($1 $2 $3)

返回前三个字段的长度。还有在 print 语句中

print $1 " is " $2

打印用“ is ”分隔的两个字段。变量和数值表达式也可以在连接中出现。

3.5. 数组
数组元素不用声明;在被提及到的时候才导致它的存在。下标可以有任何非空的值,包括非数值的字符串。作为常规的数值下标的例子,语句

x[NR] = $0
把当前输入记录赋值到数组 x 的第 NR 个元素。实际上,在原理上(尽管可能很慢)用 awk 程序按随机的次序处理整个输入是可能的

{ x[NR] = $0 }
END { ... 程序 ... }
第一动作只是把每个输入行记录到数组 x 中。

数组元素可以用非数值的值来命名,这给予 awk 非常象 Snobol 语言的关联内存表的能力。假设输入包含的字段带有象 apple、orange 等等这样的值。则程序

/apple/ { x["apple"]++ }
/orange/ { x["orange"]++ }
END { print x["apple"], x["orange"] }
增加指名的数组元素的计数,并在输入结束时打印它们。

3.6. 控制流语句
awk 提供了同 C 语言一样的基本控制流语句 if-else、while、for,和使用花括号的语句组合。我们在章节 3.3 展示了 if 语句而没有描述它。求值在圆括号中的条件;如果为真,则执行在跟随在 if 后面的语句。else 部分是可选的。

while 语句完全同 C 语言的一样。例如,要一行一个打印所有输入字段

i = 1
while (i <= NF) {
        print $i
        ++i
}
for 语句也完全同 C 的一样:

for (i = 1; i <= NF; i++)
        print $i
同上面的 while 语句做同样的工作。

for 语句还有一种可选的形式,它适合于访问关联数组的元素:

for (i in array)
        语句
把 i 依次设置为 array 的每个元素并重复执行后面的语句。元素是按明显的随机次序访问的。如果在循环期间 i 被改变了,或者访问了新元素,就会出现混乱。

在 if、while、for 的条件部分中的表达式可以包括关系算符如 <、<=、>、>=、==(“等于”)、!=(“不等于”);带有匹配算符 ~ 和 !~ 表示匹配的正则表达式;逻辑算符 ||、&& 和 !;当然还有用于组合的圆括号。

break 语句导致从围绕它 while 或 for 中立即退出,continue 语句导致开始下一次重复。

next 语句导致立即跳转到下一个记录并从头开始扫描模式。exit 语句导致程序表现得如同已经到达了输入的结束。

在 awk 程序中可以放置注释: 它们开始于字符 # 并结束于本行的结束处。比如

print x, y # 这是一个注释

--------------------------------------------------------------------------------

4. 设计
UNIX 系统已经提供一些程序,它们通过传递输入经过某种选择机制而进行操作。grep 是最早和最简单的,它只打印匹配一个单一的指定模式的所有行。egrep 提供了更一般的模式,就是说,完全一般性的正则表达式;fgrep 通过特别快的算法查找关键字的集合。

sed 提供了编辑器 ed 的大多数编辑设施,并应用于输入流之上。这些程序都不提供数值功能、逻辑关系或变量。

lex 提供了一般性的正则表达式的识别能力,并充当 C 程序生成器,在能力上是没有限制的。但使用 lex 需要 C 编程的知识,并且 lex 程序必须必须在使用之前编译和装载,所以不鼓励在简短的应用中使用。

awk 尝试填充可能性矩阵中的空白。它提供了一般性的正则表达式能力和隐含的输入/输出循环。它还提供方便的数值处理、变量、更一般性的选择和在动作中的控制流。它不需要编译和 C 语言知识。最后,awk 提供了访问行中字段的方便的方式;在这方面它是唯一的。

awk 还尝试完全整合字符串和数值,通过把所有数量都作为既是字符串又是数处理,尽可能晚的确定哪个表示是合适的。在大多数情况下用户可以简单的忽略这种区别。

开发 awk 的多数努力在于确定 awk 应该做什么与不应该做什么(例如,它不做字符串替换),和应当采用什么语法(没有显式的连接算符),而不是书写和调试代码。我们尝试使语法强力但易于使用并适于扫描文件。例如,缺乏声明和隐含的初始化,尽管对于通用编程语言是个坏主意,但对意图用于甚至是在命令行上合成的小程序的一门语言而言是需要的。

在实践中,awk 的使用适合两个广泛的范畴。其一可以叫做“报表生成”— 处理一个输入,提取计数,总和等。这也包括写琐碎的数据验证程序,比如校验一个字段只包含数值信息或特定分界符是正确配对的。文本和数值处理的组合在这种情况下是没有价值的。

第二个用途是做数据转换器,从一个程序生成的一种形式转换成另一个程序期望的另一种形式。最简单的例子只是选择字段,可能再做些重新安排。

--------------------------------------------------------------------------------

5. 实现
awk 语言的实际实现利用了 UNIX 操作系统上可用的开发工具。文法使用 yacc 规定;词法分析使用 lex;正则表达式识别器是直接从这些表达式构造出来的确定有限自动机。awk 程序被翻译成一个分析树,并接着直接用一个简单的解释器执行它。

awk 是为易于使用而不处理速度而设计;变量类型的延迟评估和分解到字段的需要使在任何情况下都难于达到高速。尽管如此,程序不是慢得不能工作。

下面的表 I 展示了在 PDP-11/70 上 UNIX 程序 wc、grep、egrep、fgrep、sed、lex 和 awk 在下列简单任务上的执行(用户+系统)时间:

1. 计数行数。
2. 打印包含“doug” 的所有行。
3. 打印包含“doug”、 “ken” 或“dmr”的所有行。
4. 打印每行的第三个字段。
5. 依次打印每行的第三和第二个字段。
6. 分别把包含“doug”、“ken”和“dmr”的所有行添加到文件“jdoug”、“jken”和 “jdmr”。
7. 打印每行并前导上“行号 :”。
8. 总和一个表的第四列。

程序 wc 只计数它输入中的字、行和字符;其他的我们都提到过。在所有情况下,输入都是使用命令 ls ?l 建立的包含 10,000 行的文件;每行都有如下形式

-rw-rw-rw- 1 ava 123 Oct 15 17:05 xxx

这个输入的总长度是 452,960 个字符。lex 的时间不包括编译和装载。

如同预期的一样,awk 不如特殊工具 wc、sed 或 grep 家族程序那么快,但是比更一般性的工具 lex 要快。在所有情况下,这些任务表达为 awk 程序同表达为其他语言一样容易;涉及字段的任务相当易于表达为 awk 程序。某些测试程序同时用 awk、sed 和 lex 展示。

任务

程序 1 2 3 4 5 8 7 8
wc 8.6
grep 11.7 13.1
egrep 6.2 11.5 11.6
fgrep 7.7 13.8 16.1
sed 10.2 11.6 15.8 29.0 30.5 16.1
lex 65.1 150.1 144.2 67.3 70.3 104.0 81.7 92.8
awk 15.0 25.6 29.9 33.3 38.9 46.4 71.4 31.1
表 I. 程序的执行时间。(单位是秒)

下面展示完成某些任务的程序。lex 程序一般长得难以展示。

awk:

1. END {print NR}
2. /doug/
3. /ken|doug|dmr/
4. {print $3}
5. {print $3, $2}
6. /ken/ {print >"jken"}
   /doug/ {print >"jdoug"}
   /dmr/ {print >"jdmr"}
7. {print NR ": " $0}
8. {sum = sum + $4}
END{print sum}

SED:

1. $=
2. /doug/p
3. /doug/p
   /doug/d
   /ken/p
   /ken/d
   /dmr/p
   /dmr/d
4. /[^ ]* [ ]*[^ ]* [ ]*\([^ ]*\) .*/s//\1/p
5. /[^ ]* [ ]*\([^ ]*\) [ ]*\([^ ]*\) .*/s//\2 \1/p
6. /ken/w jken
   /doug/w jdoug
   /dmr/w jdmr

LEX:

1. %{
        int i;
   %}
   %%
   \n i++;
   . ;
   %%
   yywrap() {
        printf("%d\n", i);
   }
2. %%
   ^.*doug.*$ printf("%s\n", yytext);
   . ;
   \n ;

--------------------------------------------------------------------------------

引用
1. K. Thompson and D. M. Ritchie, UNIX Programmer’s Manual, Bell Laboratories (May 1975). Sixth Edition

2. B. W. Kernighan and D. M. Ritchie, The C Programming Language, Prentice-Hall, Englewood Cliffs, New Jersey (1978).

3. M. E. Lesk, “Lex — A Lexical Analyzer Generator,” Comp. Sci. Tech. Rep. No. 39, Bell Laboratories, Murray Hill, New Jersey (1975).

4. S. C. Johnson, “Yacc — Yet Another Compiler-Compiler,” Comp. Sci. Tech. Rep. No. 32, Bell Laboratories, Murray Hill, New Jersey (July 1975).

[ Last edited by 无奈何 on 2006-10-27 at 03:16 AM ]
作者: electronixtar     时间: 2006-10-27 02:56
顶一楼。无奈何兄下次是不是发 GREP 了,呵呵?
作者: redtek     时间: 2006-10-27 03:13
超级过瘾!!收藏起来~:)
作者: vkill     时间: 2006-10-27 03:15
斑竹是不是 grep、sed、gawk、sort、wget 这及格轮着说啊~太好了
作者: 无奈何     时间: 2006-10-27 03:29
贴文章不是最终目的,希望朋友们有时间学一下。这几个命令学好了处理文本不成问题。我是有感于 CMD 文字处理能力太弱了。关于其他几个命令的文章太少了也比较简单,看看附带的帮助基本就够用了,我尝试能不能整理出一些来。
作者: wtp791211     时间: 2006-10-27 04:57
虽然看不在懂,还是顶一下,
作者: ccwan     时间: 2006-10-27 20:13
兄用心良苦,在此谢过了。严重支持。
作者: henrya2     时间: 2007-3-24 07:35
Sed & awk好像是Unix下的工具,也有DOS版?
作者: xycoordinate     时间: 2007-3-25 00:19
这个还是到chinaunix论坛看看去!
作者: estar     时间: 2007-4-9 10:57
今天再次用到了awk用到了版主的文章,顶
作者: woainiphj     时间: 2007-7-4 00:57    标题: DOS

大家都是高手,,,,我了也不会输的
作者: wbshu     时间: 2007-7-6 10:02
还是在论坛里面能找到好东西,谢了!
作者: wordexport     时间: 2007-9-29 16:00
顶着!以后慢慢研究
作者: aa2206     时间: 2007-12-6 03:03
好东西 ,好好学习
作者: 81291895     时间: 2007-12-6 03:41
学习了
谢谢