Board logo

标题: [转载教程] Shell基础十二篇之:awk [打印本页]

作者: Batcher    时间: 2009-4-1 23:06     标题: Shell基础十二篇之:awk

  1. 下面没有讲述awk的全部特性,也不涉及awk的深层次编程,仅讲述使用awk执行行操作及怎样从文本文件和字符串中抽取信息。
  2. 内容有:抽取域、匹配正则表达式、比较域、向awk传递参数、基本的awk行操作和脚本。
  3. awk语言的最基本功能是在文件或字符串中基于指定规则浏览和抽取信息。awk抽取信息后,才能进行其他文本操作。完整的awk脚本通常用来格式化文本文件中的信息。
  4. 1 调用awk
  5. 有三种方式调用awk,第一种是命令行方式,如:awk [-F fild-separator] 'commands' input-file(s)
  6. 这里,commands是真正的awk命令。
  7. 上面例子中, [-F域分隔符]是可选的,因为awk使用空格作为缺省的域分隔符,因此如果要浏览域间有空格的文本,不必指定这个选项,但如果要浏览诸如passwd文件,此文件各域以冒号作为分隔符,则必须指明- F选项,如:awk -F:  'commands' input-file(s)
  8. 第二种方法是将所有awk命令插入一个文件,并使awk程序可执行,然后用awk命令解释器作为脚本的首行,以便通过键入脚本名称来调用它。
  9. 第三种方式是将所有的awk命令插入一个单独文件,然后调用:awk -f awk-script-file input-files(s)
  10. - f选项指明在文件awk_script_file中的awk脚本, input_file(s)是使用awk进行浏览的文件名。
  11. 2 awk脚本
  12. 在命令中调用awk时,awk脚本由各种操作和模式组成。
  13. 如果设置了- F选项,则awk每次读一条记录或一行,并使用指定的分隔符分隔指定域,但如果未设置- F选项,awk假定空格为域分隔符,并保持这个设置直到发现一新行。当新行出现时,awk命令获悉已读完整条记录,然后在下一个记录启动读命令,这个读进程将持续到文件尾或文件不再存在。
  14. 参照表,awk每次在文件中读一行,找到域分隔符(这里是符号#),设置其为域n,直至一新行(这里是缺省记录分隔符),然后,划分这一行作为一条记录,接着awk再次启动下一行读进程。
  15. awk读文件记录的方式
  16. 域1                    分隔符        域2        分隔符    域3     分隔符     域4及换行
  17. P. B u n n y (记录1 )     #         0 2 / 9 9       #      4 8         #      Yellow \n
  18. J . Tr o l l (记录2 )     #         0 7 / 9 9       #      4 8 4 2     #      Brown-3 \n
  19. 2.1 模式和动作
  20. 任何awk语句都由模式和动作组成。在一个awk脚本中可能有许多语句。模式部分决定动作语句何时触发及触发事件。处理即对数据进行的操作。如果省略模式部分,动作将时刻保持执行状态。
  21. 模式可以是任何条件语句或复合语句或正则表达式。模式包括两个特殊字段BEGIN和END。使用BEGIN语句设置计数和打印头。BEGIN语句使用在任何文本浏览动作之前,之后文本浏览动作依据输入文件开始执行。END语句用来在awk完成文本浏览动作后打印输出文本总数和结尾状态标志。如果不特别指明模式,awk总是匹配或打印行数。
  22. 实际动作在大括号{ }内指明。动作大多数用来打印,但是还有些更长的代码诸如if和循环(looping)语句及循环退出结构。如果不指明采取动作,awk将打印出所有浏览出来的记录。
  23. 2. 域和记录
  24. awk执行时,其浏览域标记为$1,$2 . . . $n。这种方法称为域标识。使用这些域标识将更容易对域进行进一步处理。
  25. 使用$1, $3表示参照第1和第3域,注意这里用逗号做域分隔。如果希望打印一个有5个域的记录的所有域,不必指明$1, $2, $3, $4, $5,可使用$0,意即所有域。Awk浏览时,到达一新行,即假定到达包含域的记录末尾,然后执行新记录下一行的读动作,并重新设置域分隔。
  26. 注意执行时不要混淆符号$和shell提示符$,它们是不同的。
  27. 为打印一个域或所有域,使用print命令。这是一个awk动作(动作语法用圆括号括起来)。
  28. 1. 抽取域
  29. 真正执行前看几个例子,现有一文本文件grade.txt,记录了一个称为柔道数据库的行信息。
  30. $cat grade.txt
  31. M.Tans 5/99 48311 Green 8 40 44
  32. J.Lulu 06/99 48317 green 9 24 26
  33. P.Bunny 02/99 48 Yellow 12 35 28
  34. J.Troll 07/99 4842 Brown-3 12 26 26
  35. L.Tansl 05/99   4712 Brown-2 12 30 28
  36. 此文本文件有7个域,即(1)名字、(2)升段日期、(3)学生序号、(4)腰带级别、(5)年龄、(6)目前比赛积分、(7)比赛最高分。
  37. 因为域间使用空格作为域分隔符,故不必用- F选项划分域,现浏览文件并导出一些数据。在例子中为了利于显示,将空格加宽使各域看得更清晰。
  38. 2. 保存awk输出
  39. 有两种方式保存shell提示符下awk脚本的输出。最简单的方式是使用输出重定向符号>文件名,下面的例子重定向输出到文件wow。
  40. $awk '{print $0}' grade.txt >wow
  41. $cat grade.txt
  42. 使用这种方法要注意,显示屏上不会显示输出结果。因为它直接输出到文件。只有在保证输出结果正确时才会使用这种方法。它也会重写硬盘上同名数据。
  43. 第二种方法是使用tee命令,在输出到文件的同时输出到屏幕。在测试输出结果正确与否时多使用这种方法。例如输出重定向到文件delete_me_and_die,同时输出到屏幕。使用这种方法,在awk命令结尾写入| tee delete_me_and_die。
  44. $awk '{print $0}' grade.txt | tee delete_me_and_die
  45. 3. 使用标准输入
  46. 在深入讲解这一章之前,先对awk脚本的输入方法简要介绍一下。实际上任何脚本都是从标准输入中接受输入的。为运行本章脚本,使用awk脚本输入文件格式,例如:belts.awk grade_student.txt
  47. 也可替代使用下述格式:
  48. 使用重定向方法:
  49. belts.awk < grade2.txt
  50. 或管道方法:
  51. grade2.txt | belts.awk
  52. 4. 打印所有记录
  53. $awk '{print $0}' grade.txt
  54. awk读每一条记录。因为没有模式部分,只有动作部分{print $0}(打印所有记录),这个动作必须用花括号括起来。上述命令打印整个文件。
  55. 5. 打印单独记录
  56. 假定只打印学生名字和腰带级别,通过查看域所在列,可知为field-1和field-4,因此可以使用$1和$4,但不要忘了加逗号以分隔域。
  57. $awk '{print $1,$4}' grade.txt
  58. M.Tans Green
  59. J.Lulu green
  60. P.Bunny Yellow
  61. J.Troll Brown-3
  62. L.Tansl Brown-2
  63. 6. 打印报告头
  64. 上述命令输出在名字和腰带级别之间用一些空格使之更容易划分,也可以在域间使用tab键加以划分。为加入tab键,使用tab键速记引用符\t,后面将对速记引用加以详细讨论。也可以为输出文本加入信息头。本例中加入name和belt及下划线。下划线使用\n,强迫启动新行,并在\n下一行启动打印文本操作。打印信息头放置在BEGIN模式部分,因为打印信息头被界定为一个动作,必须用大括号括起来。在awk查看第一条记录前,信息头被打印。
  65. $awk 'BEGIN {print "Name Belt\n-----------------------------------"}{print $1"\t",$4}' grade.txt
  66. Name Belt
  67. -----------------------------------
  68. M.Tans   Green
  69. J.Lulu   green
  70. P.Bunny  Yellow
  71. J.Troll  Brown-3
  72. L.Tansl  Brown-2
  73. 7. 打印信息尾
  74. 如果在末行加入end of report信息,可使用END语句。END语句在所有文本处理动作执行完之后才被执行。END语句在脚本中的位置放置在主要动作之后。下面简单打印头信息并告之查询动作完成。
  75. $awk 'BEGIN {print "Name\n--------"}{print $1} END {print "end-of-report"}' grade.txt
  76. Name
  77. --------
  78. M.Tans
  79. J.Lulu
  80. P.Bunny
  81. J.Troll
  82. L.Tansl
  83. end-of-report
  84. 8. awk错误信息提示
  85. 几乎可以肯定,在使用awk时,将会在命令中碰到一些错误。awk将试图打印错误行,但由于大部分命令都只在一行,因此帮助不大。
  86. 系统给出的显示错误信息提示可读性不好。使用上述例子,如果丢了一个双引号, awk将返回:
  87. $awk 'BEGIN {print "Name\n--------}{print $1} END {"end-of-report"}' grade.txt
  88. awk: cmd. line:1: BEGIN {print "Name\n--------}{print $1} END {"end-of-report"}
  89. awk: cmd. line:1:                                                            ^ unterminated string
  90. 当第一次使用awk时,可能被错误信息搅得不知所措,但通过长时间和不断的学习,可总结出以下规则。在碰到awk错误时,可相应查找:
  91. 确保整个awk命令用单引号括起来。
  92. 确保命令内所有引号成对出现。
  93. 确保用花括号括起动作语句,用圆括号括起条件语句。
  94. 可能忘记使用花括号,也许你认为没有必要,但awk不这样认为,将按之解释语法。
  95. 如果查询文件不存在,将得到下述错误信息:
  96. $awk 'END {print NR}' grades.txt
  97. awk: cmd. line:2: fatal: cannot open file 'grades.txt' for reading (没有那个文件或目录)
  98. 9.awk 键盘输入
  99. 如果在命令行并没有输入文件grade.txt,将会怎样?
  100. $awk 'BEGIN {print "Name\n--------"}{print $1} END {"end-of-report"}'
  101. Name
  102. --------
  103. BEGIN部分打印了文件头,但awk最终停止操作并等待,并没有返回shell提示符。这是因为awk期望获得键盘输入。因为没有给出输入文件,awk假定下面将会给出。如果愿意,顺序输入相关文本,并在输入完成后敲<Ctrl-D>键。如果敲入了正确的域分隔符,awk会像第一个例子一样正常处理文本。这种处理并不常用,因为它大多应用于大量的打印稿。
  104. 2.3awk中正则表达式及其操作
  105. 在grep一章中,有许多例子用到正则表达式,这里将不使用同样的例子,但可以使用条件操作讲述awk中正则表达式的用法。
  106. 这里正则表达式用斜线括起来。例如,在文本文件中查询字符串Green,使用/Green/可以查出单词Green的出现情况。
  107. 2.4元字符
  108. 这里是awk中正则表达式匹配操作中经常用到的字符,详细情况请参阅本书第7章正则表达式概述。
  109. \ ^ $. [] | () * + ?
  110. 这里有两个字符第7章没有讲到,因为它们只适用于awk而不适用于grep或sed。它们是:
  111. + 使用+匹配一个或多个字符。
  112. ?匹配模式出现频率。例如使用/X Y?Z/匹配X Y Z或Y Z。
  113. 条件操作符
  114. awk条件操作符
  115. 操作符描述操作符描述
  116. < 小于
  117. >= 大于等于
  118. <= 小于等于
  119. == 等于
  120. ~ 匹配正则表达式
  121. !~ 不匹配正则表达式
  122. != 不等于
  123. 1. 匹配
  124. 为使一域号匹配正则表达式,使用符号'~'后紧跟正则表达式,也可以用if语句。awk中if后面的条件用()括起来。
  125. 观察文件grade.txt,如果只要显示brown腰带级别可知其所在域为field-4,这样可以写出表达式{if($4~/brown/) print}意即如果field-4包含brown,打印它。如果条件满足,则打印匹配记录行。可以编写下面脚本,因为这是一个动作,必须用花括号{ }括起来。
  126. [root@Linux_chenwy sam]# awk '{if($4~/Brown/) print $0}' grade.txt
  127. J.Troll 07/99 4842 Brown-3 12 26 26
  128. L.Tansl 05/99 4712 Brown-2 12 30 28
  129. 匹配记录找到时,如果不特别声明, awk缺省打印整条记录。使用if语句开始有点难,但不要着急,因为有许多方法可以跳过它,并仍保持同样结果。下面例子意即如果记录包含模式brown,就打印它:
  130. [root@Linux_chenwy sam]# awk '$0 ~ /Brown/' grade.txt
  131. J.Troll 07/99 4842 Brown-3 12 26 26
  132. L.Tansl 05/99 4712 Brown-2 12 30 28
  133. 2. 精确匹配
  134. 假定要使字符串精确匹配,比如说查看学生序号48,文件中有许多学生序号包含48,如果在field-3中查询序号48,awk将返回所有序号带48的记录:
  135. [root@Linux_chenwy sam]# awk '{if($3~/48/) print$0}' grade.txt
  136. M.Tans 5/99 48311 Green 8 40 44
  137. J.Lulu 06/99 48317 green 9 24 26
  138. P.Bunny 02/99 48 Yellow 12 35 28
  139. J.Troll 07/99 4842 Brown-3 12 26 26
  140. 为精确匹配48,使用等号==,并用单引号括起条件。例如$3
  141. [root@Linux_chenwy sam]# awk '$3=="48" {print$0}' grade.txt
  142. P.Bunny 02/99 48 Yellow 12 35 28
  143. [root@Linux_chenwy sam]# awk '{if($3=="48") print$0}' grade.txt
  144. P.Bunny 02/99 48 Yellow 12 35 28
  145. 3. 不匹配
  146. 有时要浏览信息并抽取不匹配操作的记录,与~相反的符号是!~,意即不匹配。像原来使用查询brown腰带级别的匹配操作一样,现在看看不匹配情况。表达式$0 !~/brown/,意即查询不包含模式brown腰带级别的记录并打印它。
  147. 注意,缺省情况下, awk将打印所有匹配记录,因此这里不必加入动作部分。
  148. [root@Linux_chenwy sam]# awk '$0 !~ /Brown/' grade.txt
  149. M.Tans 5/99 48311 Green 8 40 44
  150. J.Lulu 06/99 48317 green 9 24 26
  151. P.Bunny 02/99 48 Yellow 12 35 28
  152. 可以只对field-4进行不匹配操作,方法如下:
  153. [root@Linux_chenwy sam]# awk '{if($4~/Brown/) print $0}' grade.txt
  154. J.Troll 07/99 4842 Brown-3 12 26 26
  155. L.Tansl 05/99 4712 Brown-2 12 30 28
  156. 如果只使用命令awk$4 !="brown"{print $0} grade.txt,将返回错误结果,因为用引号括起了brown,将只匹配'brown而不匹配brown-2和brown-3,当然,如果想要查询非brown-2的腰带级别,可做如下操作:
  157. [root@Linux_chenwy sam]# awk '$4!="Brown-2" {print $0}' grade.txt
  158. M.Tans 5/99 48311 Green 8 40 44
  159. J.Lulu 06/99 48317 green 9 24 26
  160. P.Bunny 02/99 48 Yellow 12 35 28
  161. J.Troll 07/99 4842 Brown-3 12 26 26
  162. 4. 小于
  163. 看看哪些学生可以获得升段机会。测试这一点即判断目前级别分field-6是否小于最高分field-7,在输出结果中,加入这一改动很容易。
  164. [root@Linux_chenwy sam]# awk '{if($6 < $7) print $0}' grade.txt
  165. M.Tans 5/99 48311 Green 8 40 44
  166. J.Lulu 06/99 48317 green 9 24 26
  167. 5. 小于等于
  168. 对比小于,小于等于只在操作符上做些小改动,满足此条件的记录也包括上面例子中的输出情况。
  169. [root@Linux_chenwy sam]# awk '{if($6 <= $7) print $1}' grade.txt
  170. M.Tans
  171. J.Lulu
  172. J.Troll
  173. 6. 大于
  174. [root@Linux_chenwy sam]# awk '{if($6 > $7) print $1}' grade.txt
  175. P.Bunny
  176. L.Tansl
  177. 7. 设置大小写
  178. 为查询大小写信息,可使用[ ]符号。在测试正则表达式时提到可匹配[ ]内任意字符或单词,因此若查询文件中级别为green的所有记录,不论其大小写,表达式应为'/[Gg]reen/'
  179. [root@Linux_chenwy sam]# awk '/[Gg]reen/' grade.txt
  180. M.Tans 5/99 48311 Green 8 40 44
  181. J.Lulu 06/99 48317 green 9 24 26
  182. 8. 任意字符
  183. 抽取名字,其记录第一域的第四个字符是a,使用句点.。表达式/^...a/意为行首前三个字符任意,第四个是a,尖角符号代表行首。
  184. [root@Linux_chenwy sam]# awk '$1 ~ /^...a/' grade.txt
  185. M.Tans 5/99 48311 Green 8 40 44
  186. L.Tansl 05/99 4712 Brown-2 12 30 28
  187. 9. 或关系匹配
  188. 为抽取级别为yellow或brown的记录,使用竖线符|。意为匹配|两边模式之一。注意,使用竖线符时,语句必须用圆括号括起来。
  189. [root@Linux_chenwy sam]# awk '$0 ~/(Yellow|Brown)/' grade.txt
  190. P.Bunny 02/99 48 Yellow 12 35 28
  191. J.Troll 07/99 4842 Brown-3 12 26 26
  192. L.Tansl 05/99 4712 Brown-2 12 30 28
  193. 上面例子输出所有级别为Ye l l o w或brown的记录。
  194. 使用这种方法在查询级别为Green或green时,可以得到与使用[ ]表达式相同的结果。
  195. [root@Linux_chenwy sam]# awk '/^M/' grade.txt
  196. M.Tans 5/99 48311 Green 8 40 44
  197. 10. 行首
  198. 不必总是使用域号。如果查询文本文件行首包含M的代码,可简单使用下面^符号:
  199. [root@Linux_chenwy sam]# awk '/^M/' grade.txt
  200. 复合表达式即为模式间通过使用下述各表达式互相结合起来的表达式:
  201. && AND : 语句两边必须同时匹配为真。
  202. || OR:语句两边同时或其中一边匹配为真。
  203. ! 非求逆
  204. 11. AND
  205. 打印记录,使其名字为'P.Bunny且级别为Yellow,使用表达式($1=="P.Bunny" && $4=="Yellow"),意为&&两边匹配均为真。完整命令如下:
  206. [root@Linux_chenwy sam]# awk '{if ($1=="P.Bunny" && $4=="Yellow") print $0}' grade.txt
  207. P.Bunny 02/99 48 Yellow 12 35 28
  208. 12. Or
  209. 如果查询级别为Yellow或brown,使用或命令。意为"||"符号两边的匹配模式之一或全部为真。
  210. [root@Linux_chenwy sam]# awk '{if ($4=="Yellow" || $4~/Brown/) print $0}' grade.txt
  211. P.Bunny 02/99 48 Yellow 12 35 28
  212. J.Troll 07/99 4842 Brown-3 12 26 26
  213. L.Tansl 05/99 4712 Brown-2 12 30 28
  214. 原来不一定得加print,下面我自己对例一二做了一下
  215. 1
  216. [root@Linux_chenwy sam]# awk '$4~/Brown/' grade.txt
  217. J.Troll 07/99 4842 Brown-3 12 26 26
  218. L.Tansl 05/99 4712 Brown-2 12 30 28
  219. 2
  220. [root@Linux_chenwy sam]# awk '$3=="48"' grade.txt
  221. P.Bunny 02/99 48 Yellow 12 35 28
  222. [root@Linux_chenwy sam]# awk '$3="48"' grade.txt
  223. M.Tans 5/99 48 Green 8 40 44
  224. J.Lulu 06/99 48 green 9 24 26
  225. P.Bunny 02/99 48 Yellow 12 35 28
  226. J.Troll 07/99 48 Brown-3 12 26 26
  227. L.Tansl 05/99 48 Brown-2 12 30 28
  228. 2中,我把=和==写错了,呵呵,一个是赋值,一个是等于
  229. awk内置变量
  230. awk有许多内置变量用来设置环境信息。这些变量可以被改变。
  231. ARGC 命令行参数个数
  232. ARGV 命令行参数排列
  233. ENVIRON 支持队列中系统环境变量的使用
  234. FILENAME awk浏览的文件名
  235. FNR 浏览文件的记录数
  236. FS 设置输入域分隔符,等价于命令行-F选项
  237. NF 浏览记录的域个数
  238. NR 已读的记录数
  239. OFS 输出域分隔符
  240. ORS 输出记录分隔符
  241. RS 控制记录分隔符
  242. ARGC支持命令行中传入awk脚本的参数个数。ARGV是ARGC的参数排列数组,其中每一元素表示为ARGV [n],n为期望访问的命令行参数。
  243. ENVIRON支持系统设置的环境变量,要访问单独变量,使用实际变量名,例如ENVIRON ["EDITOR"]="Vi"。
  244. FILENAME支持awk脚本实际操作的输入文件。因为awk可以同时处理许多文件,因此如果访问了这个变量,将告之系统目前正在浏览的实际文件。
  245. FNR支持awk目前操作的记录数。其变量值小于等于NR。如果脚本正在访问许多文件,每一新输入文件都将重新设置此变量。
  246. FS用来在awk中设置域分隔符,与命令行中-F选项功能相同。缺省情况下为空格。如果用逗号来作域分隔符,设置FS=","。
  247. NF支持记录域个数,在记录被读之后再设置。
  248. OFS允许指定输出域分隔符,缺省为空格。如果想设置为#,写入OFS="#"。
  249. ORS为输出记录分隔符,缺省为新行(\n)。
  250. RS是记录分隔符,缺省为新行(\n)。
  251. NF、NR和FILENAME
  252. 要快速查看记录个数,应使用NR。比如说导出一个数据库文件后,如果想快速浏览记录个数,以便对比于其初始状态,查出导出过程中出现的错误。使用NR将打印输入文件的记录个数。print NR放在END语法中。
  253. [root@chenwy sam]# awk 'END{print NR}' grade.txt
  254. 5
  255. 如:所有学生记录被打印,并带有其记录号。使用NF变量显示每一条读记录中有多少个域,并在END部分打印输入文件名。
  256. [root@chenwy sam]# awk '{print NF,NR,$0} END{print FILENAME}' grade.txt
  257. 7 1 M.Tans 5/99 48311 Green 8 40 44
  258. 7 2 J.Lulu 06/99 48317 green 9 24 26
  259. 7 3 P.Bunny 02/99 48 Yellow 12 35 28
  260. 7 4 J.Troll 07/99 4842 Brown-3 12 26 26
  261. 7 5 L.Tansl 05/99       4712 Brown-2 12 30 28
  262. grade.txt
  263. 在从文件中抽取信息时,最好首先检查文件中是否有记录。下面的例子只有在文件中至少有一个记录时才查询brown级别记录。使用AND复合语句实现这一功能。意即至少存在一个记录后,查询字符串brown,最后打印结果。
  264. [root@chenwy sam]# awk '{if (NR>0 && $4~/Brown/)print $0}' grade.txt
  265. J.Troll 07/99 4842 Brown-3 12 26 26
  266. L.Tansl 05/99   4712 Brown-2 12 30 28
  267. NF的一个强大功能是将变量$PWD的返回值传入awk并显示其目录。这里需要指定域分隔符/。
  268. [root@chenwy sam]# echo $PWD | awk -F/ ' {print $NF}'
  269. sam
  270. 另一个例子是显示文件名。
  271. [root@chenwy sam]# echo "/usr/local/etc/rc.sybase" | awk -F/ '{print $NF}'
  272. rc.sybase
  273. 如果不指定域分割符,返回的如下:
  274. [root@chenwy sam]# echo $PWD | awk '{print $NF}'
  275. /usr/sam
  276. [root@chenwy sam]# echo "/usr/local/etc/rc.sybase" | awk '{print $NF}'
  277. /usr/local/etc/rc.sybase
  278. awk操作符
  279. 在awk中使用操作符,基本表达式可以划分为数字型、字符串型、变量型、域及数组元素,前面已经讲过一些。下面列出其完整列表。
  280. 在表达式中可以使用下述任何一种操作符。
  281. = += *= / = %= ^ = 赋值操作符
  282. ? 条件表达操作符
  283. || && ! 并、与、非(上一节已讲到)
  284. ~!~ 匹配操作符,包括匹配和不匹配
  285. < <= == != >> 关系操作符
  286. + - * / % ^ 算术操作符
  287. + + -- 前缀和后缀
  288. 前面已经讲到了其中几种操作,下面继续讲述未涉及的部分。
  289. 1. 设置输入域到域变量名
  290. 在awk中,设置有意义的域名是一种好习惯,在进行模式匹配或关系操作时更容易理解。一般的变量名设置方式为name=$n,这里name为调用的域变量名, n为实际域号。例如设置学生域名为name,级别域名为belt,操作为name=$1;belt s=$4。注意分号的使用,它分隔awk命令。
  291. 下面例子中,重新赋值学生名域为name,级别域为belts。查询级别为Yellow的记录,并最终打印名称和级别。
  292. [sam@chenwy sam]$awk '{name=$1;belts=$4;if(belts ~/Yellow/) print name" is belt "belts}' grade.txt
  293. P.Bunny is belt Yellow
  294. 2. 域值比较操作
  295. 有两种方式测试一数值域是否小于另一数值域。
  296. 1) 在BEGIN中给变量名赋值。
  297. 2) 在关系操作中使用实际数值。
  298. 通常在BEGIN部分赋值是很有益的,可以在awk表达式进行改动时减少很多麻烦。
  299. 使用关系操作必须用圆括号括起来。
  300. 下面的例子查询所有比赛中得分在27点以下的学生。用引号将数字引用起来是可选的,"27"、27产生同样的结果。
  301. [sam@chenwy sam]$awk '{if ($6<"27") print $0}' grade.txt
  302. M.Tans 5/99 48311 Green 8 40 44
  303. J.Lulu 06/99 48317 green 9 24 26
  304. 第二个例子中给数字赋以变量名BASELINE和在BEGIN部分给变量赋值,两者意义相同。
  305. [sam@chenwy sam]$awk 'BEGIN{BASELINE="27"} {if ($6<BASELINE) print $0}' grade.txt
  306. J.Lulu 06/99 48317 green 9 24 26
  307. J.Troll 07/99 4842 Brown-3 12 26 26
  308. 3. 修改数值域取值
  309. 当在awk中修改任何域时,重要的一点是要记住实际输入文件是不可修改的,修改的只是保存在缓存里的awk复本。awk会在变量NR或NF变量中反映出修改痕迹。
  310. 为修改数值域,简单的给域标识重赋新值,如:$1=$1+5,会将域1数值加5,但要确保赋值域其子集为数值型。
  311. 修改M.Tansley的目前级别分域,使其数值从40减为39,使用赋值语句$6=$6-1,当然在实施修改前首先要匹配域名。
  312. [sam@chenwy sam]$awk '{$6=$6-1;print $1,$6,$7}' grade.txt
  313. M.Tans 39 44
  314. J.Lulu 24 26
  315. P.Bunny 35 28
  316. J.Troll 26 26
  317. L.Tansl 30 28
  318. [sam@chenwy sam]$awk '{if($1=="M.Tans") {$6=$6-1;print $1,$6,$7}}' grade.txt
  319. M.Tans 39 44
  320. 4. 修改文本域
  321. 修改文本域即对其重新赋值。需要做的就是赋给一个新的字符串。在J.Troll中加入字母,使其成为J.L.Troll,表达式为$1="J.L.Troll",记住字符串要使用双引号,并用圆括号括起整个语法。
  322. [sam@chenwy sam]$awk '{if($1=="J.Troll") $1="J.L.Troll"; print $1}' grade.txt
  323. M.Tans
  324. J.Lulu
  325. P.Bunny
  326. J.L.Troll
  327. L.Tansl
  328. 5. 只显示修改记录
  329. 上述例子均是对一个小文件的域进行修改,因此打印出所有记录查看修改部分不成问题,但如果文件很大,记录甚至超过1 0 0,打印所有记录只为查看修改部分显然不合情理。在模式后面使用花括号将只打印修改部分。取得模式,再根据模式结果实施操作,可能有些抽象,现举一例,只打印修改部分。注意花括号的位置。
  330. [sam@chenwy sam]$awk '{if($1=="J.Troll") {$1="J.L.Troll";print $1}}' grade.txt  
  331. J.L.Troll
  332. 6. 创建新的输出域
  333. 在awk中处理数据时,基于各域进行计算时创建新域是一种好习惯。创建新域要通过其他域赋予新域标识符。如创建一个基于其他域的加法新域{$4=$2+$3},这里假定记录包含3个域,则域4为新建域,保存域2和域3相加结果。
  334. 在文件grade.txt中创建新域8保存域目前级别分与域最高级别分的减法值。表达式为'{$8=$7-$6}',语法首先测试域目前级别分小于域最高级别分。新域因此只打印其值大于零的学生名称及其新域值。在BEGIN部分加入tab键以对齐报告头。
  335. [sam@chenwy sam]$awk 'BEGIN{print "Name\tDifference"}{if($6<$7) {$8=$7-$6;print $1,$8}}' grade.txt
  336. Name    Difference
  337. M.Tans 4
  338. J.Lulu 2
  339. 当然可以创建新域,并赋给其更有意义的变量名。例如:
  340. [sam@chenwy sam]$awk 'BEGIN{print "Name\tDifference"}{if($6<$7) {diff=$7-$6;print $1,diff}}' grade.txt
  341. Name    Difference
  342. M.Tans 4
  343. J.Lulu 2
  344. 7. 增加列值
  345. 为增加列数或进行运行结果统计,使用符号+=。增加的结果赋给符号左边变量值,增加到变量的域在符号右边。例如将$1加入变量total,表达式为total+=$1。列值增加很有用。许多文件都要求统计总数,但输出其统计结果十分繁琐。在awk中这很简单,请看下面的例子。
  346. 将所有学生的'目前级别分'加在一起,方法是tot+=$6,tot即为awk浏览的整个文件的域6结果总和。所有记录读完后,在END部分加入一些提示信息及域6总和。不必在awk中显示说明打印所有记录,每一个操作匹配时,这是缺省动作。
  347. [sam@chenwy sam]$awk '(tot+=$6); END{print "Club student total points :" tot}' grade.txt
  348. M.Tans 5/99 48311 Green 8 40 44
  349. J.Lulu 06/99 48317 green 9 24 26
  350. P.Bunny 02/99 48 Yellow 12 35 28
  351. J.Troll 07/99 4842 Brown-3 12 26 26
  352. L.Tansl 05/99 4712 Brown-2 12 30 28
  353. Club student total points :155
  354. 如果文件很大,你只想打印结果部分而不是所有记录,在语句的外面加上圆括号()即可。
  355. [sam@chenwy sam]$awk '{(tot+=$6)}; END{print "Club student total points :" tot}' grade.txt
  356. Club student total points :155
  357. 8. 文件长度相加
  358. 在目录中查看文件时,如果想快速查看所有文件的长度及其总和,但要排除子目录,使用ls -l命令,然后管道输出到awk,awk首先剔除首字符为d(使用正则表达式)的记录,然后将文件长度列相加,并输出每一文件长度及在END部分输出所有文件的长度。
  359. 本例中,首先用ls -l命令查看一下文件属性。注意第二个文件属性首字符为d,说明它是一个目录,文件长度是第5列,文件名是第9列。如果系统不是这样排列文件名及其长度,应适时加以改变。
  360. 下面的正则表达式表明必须匹配行首,并排除字符d,表达式为^[^d]。使用此模式打印文件名及其长度,然后将各长度相加放入变量tot中。
  361. [sam@chenwy sam]$ls -l | awk '/^[^d]/ {print $9"\t"$5} {tot+=$5} END {print "total KB:" tot}'
  362. ...................
  363. total KB:174144
  364. awk内置字符串函数
  365. gsub(r,s) 在整个$0中用s替代r
  366. gsub(r,s,t) 在整个t中用s替代r
  367. index(s,t) 返回s中字符串t的第一位置
  368. length(s) 返回s长度
  369. match(s,r) 测试s是否包含匹配r的字符串
  370. split(s,a,fs) 在fs上将s分成序列a
  371. sprint (fmt,exp) 返回经fmt格式化后的exp
  372. sub(r,s) 用$0中最左边最长的子串代替s
  373. substr(s,p) 返回字符串s中从p开始的后缀部分
  374. substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分
  375. gsub函数有点类似于sed查找和替换。它允许替换一个字符串或字符为另一个字符串或字符,并以正则表达式的形式执行。第一个函数作用于记录$0,第二个gsub函数允许指定目标,然而,如果未指定目标,缺省为$0。
  376. index(s,t)函数返回目标字符串s中查询字符串t的首位置。
  377. length函数返回字符串s字符长度。
  378. match函数测试字符串s是否包含一个正则表达式r定义的匹配。
  379. split使用域分隔符fs将字符串s划分为指定序列a。
  380. sprint函数类似于printf函数(以后涉及),返回基本输出格式fmt的结果字符串exp。
  381. sub(r,s)函数将用s替代$0中最左边最长的子串,该子串被(r)匹配。
  382. sub(s,p)返回字符串s在位置p后的后缀。
  383. substr(s,p,n)同上,并指定子串长度为n。
  384. 现在看一看awk中这些字符串函数的功能。
  385. 1. gsub
  386. 要在整个记录中替换一个字符串为另一个,使用正则表达式格式, /目标模式/,替换模式/。例如改变学生序号4842到4899:
  387. [root@Linux_chenwy root]# cd /usr/sam
  388. [root@Linux_chenwy sam]# awk 'gsub(/4842/,4899){print $0}' grade.txt
  389. J.Troll 07/99 4899 Brown-3 12 26 26
  390. [root@Linux_chenwy sam]# awk 'gsub(/4842/,4899)' grade.txt
  391. J.Troll 07/99 4899 Brown-3 12 26 26
  392. 2. index
  393. 查询字符串s中t出现的第一位置。必须用双引号将字符串括起来。例如返回目标字符串Bunny中ny出现的第一位置,即字符个数。
  394. [root@Linux_chenwy sam]# awk 'BEGIN {print index("Bunny","ny")}' grade.txt
  395. 4
  396. 3. length
  397. 返回所需字符串长度,例如检验字符串J.Troll返回名字及其长度,即人名构成的字符个数
  398. [root@Linux_chenwy sam]# awk '$1=="J.Troll" {print length($1)" "$1}' grade.txt
  399. 7 J.Troll
  400. 还有一种方法,这里字符串加双引号。
  401. [root@Linux_chenwy sam]# awk 'BEGIN{print length("A FEW GOOD MEN")}'
  402. 14
  403. 4. match
  404. match测试目标字符串是否包含查找字符的一部分。可以对查找部分使用正则表达式,返回值为成功出现的字符排列数。如果未找到,返回0,第一个例子在ANCD中查找d。因其不存在,所以返回0。第二个例子在ANCD中查找D。因其存在,所以返回ANCD中D出现的首位置字符数。第三个例子在学生J.Lulu中查找u。
  405. [root@Linux_chenwy sam]# awk 'BEGIN{print match("ANCD",/d/)}'
  406. 0
  407. [root@Linux_chenwy sam]# awk 'BEGIN{print match("ANCD",/D/)}'
  408. 4
  409. [root@Linux_chenwy sam]# awk '$1=="J.Lulu" {print match($1,"u")}' grade.txt
  410. 4
  411. 5. split
  412. 使用split返回字符串数组元素个数。工作方式如下:如果有一字符串,包含一指定分隔符-,例如AD2-KP9-JU2-LP-1,将之划分成一个数组。使用split,指定分隔符及数组名。此例中,命令格式为("AD2-KP9-JU2-LP-1",parts_array,"-"),split然后返回数组下标数,这里结果为4。
  413. [root@Linux_chenwy sam]# awk 'BEGIN {print split("123-456-789",pats_array,"-")}'
  414. 3
  415. 还有一个例子使用不同的分隔符。
  416. [root@Linux_chenwy sam]# awk 'BEGIN {print split("123#456#789",myarray,"#")}'                    3
  417. 这个例子中,split返回数组myarray的下标数。数组myarray取值如下:
  418. myarray[1]=123
  419. myarray[2]=456
  420. myarray[3]=789
  421. 结尾部分讲述数组概念。
  422. 6. sub
  423. 使用sub发现并替换模式的第一次出现位置。字符串STR包含'poped popo pill',执行下列sub命令sub(/op/,"OP",STR)。模式op第一次出现时,进行替换操作,返回结果如下:'pOPed pope pill'。
  424. 如:学生J.Troll的记录有两个值一样,"目前级别分"与"最高级别分"。只改变第一个为29,第二个仍为26不动,操作命令为sub(/26/,"29",$0),只替换第一个出现26的位置。注意J.Troll记录需存在。
  425. [root@Linux_chenwy sam]# awk '$1=="J.Troll" sub(/26/,"29",$0)' grade.txt
  426. M.Tans 5/99 48311 Green 8 40 44
  427. J.Lulu 06/99 48317 green 9 24 29
  428. P.Bunny 02/99 48 Yellow 12 35 28
  429. J.Troll 07/99 4842 Brown-3 12 29 26
  430. L.Tansl 05/99 4712 Brown-2 12 30 28
  431. 7. substr
  432. substr是一个很有用的函数。它按照起始位置及长度返回字符串的一部分。例子如下:
  433. [root@Linux_chenwy sam]# awk '$1=="L.Tansl" {print substr($1,1,3)}' grade.txt
  434. L.T
  435. 上面例子中,指定在域1的第一个字符开始,返回其前面3个字符。
  436. 如果给定长度值远大于字符串长度, awk将从起始位置返回所有字符,要抽取LTansl-ey的姓,只需从第3个字符开始返回长度为7。可以输入长度99,awk返回结果相同。
  437. [root@Linux_chenwy sam]# awk '$1=="L.Tansl" {print substr($1,1,99)}' grade.txt
  438. L.Tansl
  439. substr的另一种形式是返回字符串后缀或指定位置后面字符。这里需要给出指定字符串及其返回字串的起始位置。例如,从文本文件中抽取姓氏,需操作域1,并从第三个字符开始:
  440. [root@Linux_chenwy sam]# awk '{print substr($1,3)}' grade.txt
  441. Tans
  442. Lulu
  443. Bunny
  444. Troll
  445. Tansl
  446. 还有一个例子,在BEGIN部分定义字符串,在END部分返回从第t个字符开始抽取的子串。
  447. [root@Linux_chenwy sam]# awk 'BEGIN{STR="A FEW GOOD MEN"}END{print substr(STR,7)}' grade.txt
  448. GOOD MEN
  449. 8. 从shell中向awk传入字符串
  450. awk脚本大多只有一行,其中很少是字符串表示的。大多要求在一行内完成awk脚本,这一点通过将变量传入awk命令行会变得很容易。现就其基本原理讲述一些例子。
  451. 使用管道将字符串stand-by传入awk,返回其长度。
  452. [root@Linux_chenwy sam]# echo "Stand-by" | awk '{print length($0)}'
  453. 8
  454. 设置文件名为一变量,管道输出到awk,返回不带扩展名的文件名。
  455. [root@Linux_chenwy sam]# STR="mydoc.txt"
  456. [root@Linux_chenwy sam]# echo $STR|awk '{print substr($STR,1,5)}'
  457. mydoc
  458. 设置文件名为一变量,管道输出到awk,只返回其扩展名。
  459. [root@Linux_chenwy sam]# STR="mydoc.txt"
  460. [root@Linux_chenwy sam]# echo $STR|awk '{print substr($STR,7)}'
  461. txt
  462. 字符串屏蔽序列
  463. 使用字符串或正则表达式时,有时需要在输出中加入一新行或查询一元字符。
  464. 打印一新行时(新行为字符\n),给出其屏蔽序列,以不失其特殊含义,用法为在字符串前加入反斜线。例如使用\n强迫打印一新行。
  465. 如果使用正则表达式,查询花括号({ }),在字符前加反斜线,如/\{/,将在awk中失掉其特殊含义。
  466. awk中使用的屏蔽序列
  467. \b 退格键
  468. \t tab键
  469. \f 走纸换页
  470. \ddd 八进制值
  471. \n 新行
  472. \c 任意其他特殊字符,例如\ \为反斜线符号
  473. \r 回车键
  474. 使用上述符号,打印May Day,中间夹tab键,后跟两个新行,再打印May Day,但这次使用八进制数104、141、171分别代表D、a、y。
  475. [root@chenwy sam]# awk 'BEGIN {print"\nMay\tDay\n\nMay\t\104\141\171"}'
  476. May     Day
  477. May     Day
  478. 注意,\104为D的八进制ASCII码,\141为a的八进制ASCII码,等等。
  479. awk输出函数printf
  480. 目前为止,所有例子的输出都是直接到屏幕,除了tab键以外没有任何格式。awk提供函数printf,拥有几种不同的格式化输出功能。例如按列输出、左对齐或右对齐方式。
  481. 每一种printf函数(格式控制字符)都以一个%符号开始,以一个决定转换的字符结束.转换包含三种修饰符。
  482. printf函数基本语法是printf([格式控制符],参数),格式控制字符通常在引号里。
  483. printf修饰符
  484. - 左对齐
  485. Width 域的步长,用0表示0步长
  486. .prec 最大字符串长度,或小数点右边的位数
  487. 表9-7 awk printf格式
  488. %c ASCII字符
  489. %d 整数
  490. %e 浮点数,科学记数法
  491. %f 浮点数,例如(1 2 3 . 4 4)
  492. %g awk决定使用哪种浮点数转换e或者f
  493. %o 八进制数
  494. %s 字符串
  495. %x 十六进制数
  496. 1. 字符转换
  497. 观察ASCII码中65的等价值。管道输出65到awk。printf进行ASCII码字符转换。这里也加入换行,因为缺省情况下printf不做换行动作。
  498. A[sam@chenwy sam]$echo "65" | awk '{printf "%c\n",$0}'
  499. A
  500. 按同样方式使用awk得到同样结果。
  501. [sam@chenwy sam]$awk 'BEGIN{printf "%c\n",65}'
  502. A
  503. 所有的字符转换都是一样的,下面的例子表示进行浮点数转换后'999'的输出结果。整数传入后被加了六个小数点。
  504. [sam@chenwy sam]$awk 'BEGIN{printf "%f\n",999}'
  505. 999.000000
  506. 2. 格式化输出
  507. 打印所有的学生名字和序列号,要求名字左对齐, 15个字符长度,后跟序列号。注意\n换行符放在最后一个指示符后面。输出将自动分成两列。
  508. [root@chenwy sam]# awk '{printf "%-15s %s\n",$1,$3}' grade.txt
  509. M.Tans          48311
  510. J.Lulu          48317
  511. P.Bunny         48
  512. J.Troll         4842
  513. L.Tansl         4712
  514. 加入一些文本注释帮助理解报文含义。可在正文前嵌入头信息。注意这里使用print加入头信息。如果愿意,也可使用printf。
  515. [root@chenwy sam]# awk 'BEGIN{print "Name\t\tS.Number"}{printf "%-15s %s\n",$1,$3}' grade.txt
  516. Name            S.Number
  517. M.Tans          48311
  518. J.Lulu          48317
  519. P.Bunny         48
  520. J.Troll         4842
  521. L.Tansl         4712
  522. 3.向一行awk命令传值
  523. 在查看awk脚本前,先来查看怎样在awk命令行中传递变量。
  524. 在awk执行前将值传入awk变量,需要将变量放在命令行中,格式如下:
  525. awk 命令变量=输入文件值
  526. (后面会讲到怎样传递变量到awk脚本中)。
  527. 下面的例子在命令行中设置变量AGE等于10,然后传入awk中,查询年龄在10岁以下的所有学生。
  528. [root@chenwy sam]# awk '{if ($5<AGE) print $0}' AGE=10 grade.txt
  529. M.Tans 5/99 48311 Green 8 40 44
  530. J.Lulu 06/99 48317 green 9 24 26
  531. 要快速查看文件系统空间容量,观察其是否达到一定水平,可使用下面awk一行脚本。因为要监视的已使用空间容量不断在变化,可以在命令行指定一个触发值。首先用管道命令将df -k 传入awk,然后抽出第4列,即剩余可利用空间容量。使用$4~/^[0-9] /取得容量数值(1024块)而不是df的文件头,然后对命令行与'if($4<TRIGGER)'上变量TRIGGER中指定的值进行查询测试。
  532. [root@chenwy sam]# df -k|awk '{if($4<TRIGGER) print $6"\t"$4}' TRIGGER=560000
  533. /boot   458589
  534. /dev/shm        99352
  535. [root@chenwy sam]# df -k|awk '($4~/^[0-9]/) {if($4<TRIGGER) print $6"\t"$4}' TRIGGER=5600000
  536. /       2610716
  537. /boot   458589
  538. /dev/shm        99352
  539. 在系统中使用df -k命令,产生下列信息:
  540. [root@chenwy sam]# df -k
  541. 文件系统               1K-块        已用     可用 已用% 挂载点
  542. /dev/sda2              5162828   2289804   2610764  47% /
  543. /dev/sda1               497829     13538    458589   3% /boot
  544. none                     99352         0     99352   0% /dev/shm
  545. 如果系统中df输出格式不同,必须相应改变列号以适应工作系统。
  546. 当然可以使用管道将值传入awk。本例使用who命令, who命令第一列包含注册用户名,这里打印注册用户,并加入一定信息。
  547. [sam@chenwy sam]$who |awk '{print $1" is logged on"}'
  548. root is logged on
  549. root is logged on
  550. [sam@chenwy sam]$who
  551. root     :0           Nov 23 20:17
  552. root     pts/0        Nov 23 20:25 (:0.0)
  553. awk也允许传入环境变量。下面的例子使用环境变量HOME支持当前用户目录。可从pwd命令管道输出到awk中获得相应信息。
  554. [sam@chenwy sam]$pwd | awk '{if ($1==derr) print $1}' derr=$HOME
  555. /usr/sam
  556. 4. awk脚本文件
  557. 可以将awk脚本写入一个文件再执行它。命令不必很长(尽管这是写入一个脚本文件的主要原因),甚至可以接受一行命令。这样可以保存awk命令,以使不必每次使用时都需要重新输入。使用文件的另一个好处是可以增加注释,以便于理解脚本的真正用途和功能。
  558. 使用前面的几个例子,将之转换成awk可执行文件。像原来做的一样,将学生目前级别分相加awk '(tot+=$6) END{print "club student total points:" tot}' grade.txt。
  559. 创建新文件student_tot.awk,给所有awk程序加入awk扩展名是一种好习惯,这样通过查看文件名就知道这是一个awk程序。文本如下:
  560. [sam@chenwy sam]$cat student_tot.awk
  561. #!/bin/awk -f
  562. #all commnet lines must start with a hash '#'
  563. #name:students_tots.awk
  564. #to call:student_tot.awk grade.txt
  565. #prints total and average of club student points
  566. #print a header first
  567. BEGIN{
  568. print "Student  Date  Member  No.  Grade Age  Points Max"
  569. print "Name     Joined                        Gained  Point Available"
  570. print "=============================================================="
  571. }
  572. #let's add the scores of points gained
  573. (tot+=$6)
  574. #finished proessing now let's print the total and average point
  575. END{
  576. print "Club student total points :" tot
  577. print "Average Club Student Points:" tot/NR}
  578. 通过将命令分开,脚本可读性提高,还可以在命令之间加入注释。这里加入头信息和结尾的平均值。基本上这是一个一行脚本文件。执行时,在脚本文件后键入输入文件名,但是首先要对脚本文件加入可执行权限。
  579. [sam@chenwy sam]$chmod u+x student_tot.awk
  580. [sam@chenwy sam]$./student_tot.awk grade.txt
  581. Student  Date  Member  No.  Grade Age  Points Max
  582. Name     Joined                        Gained  Point Available
  583. ==============================================================
  584. M.Tans 5/99 48311 Green 8 40 44
  585. J.Lulu 06/99 48317 green 9 24 26
  586. P.Bunny 02/99 48 Yellow 12 35 28
  587. J.Troll 07/99 4842 Brown-3 12 26 26
  588. L.Tansl 05/99 4712 Brown-2 12 30 28
  589. Club student total points :155
  590. Average Club Student Points:31
  591. 过滤相同行:
  592. 如有一个文件strip中有多条重复错误提法:
  593. [sam@Linux_chenwy sam]$cat strip
  594. etreiytrpytyu
  595. ERROR*
  596. ERROR*
  597. ERROR*
  598. ERROR*
  599. IUEWROPYJRTMELUYK
  600. ERROR*
  601. ERROR*
  602. ERROR*
  603. ERROR*
  604. ERROR*
  605. ERROR*
  606. EWUTIRWJYHT
  607. ERROR*
  608. ERROR*
  609. JGIOERYO56ERU
  610. ERROR*
  611. ERROR*
  612. ERROR*
  613. JGEORYKP65EKU;YK,
  614. 现在用awk脚本过滤出错误行的出现频率,使得每一个失败记录只对应一个错误行。awk脚本如下:
  615. [sam@Linux_chenwy sam]$cat error_strip.awk
  616. #!/bin/awk -f
  617. #error_strip.awk
  618. #to call:error_strip.awk<filename>
  619. #strips out the ERROR* lines if there are more than one
  620. #ERROR* lines after each failed record.
  621. BEGIN {error_line=""}
  622. #tell awk the whole is "ERROR*"
  623. {if ($0=="ERROR*" && error_line=="ERROR*")
  624. #go to next line
  625. next;
  626. error_line=$0;print}
  627. 执行结果如下:
  628. [sam@Linux_chenwy sam]$./error_strip.awk strip
  629. etreiytrpytyu
  630. ERROR*
  631. IUEWROPYJRTMELUYK
  632. ERROR*
  633. EWUTIRWJYHT
  634. ERROR*
  635. JGIOERYO56ERU
  636. ERROR*
  637. JGEORYKP65EKU;YK,
  638. 5. 在awk中使用FS变量
  639. 如果使用非空格符做域分隔符(FS)浏览文件,例如#或:,编写这样的一行命令很容易,因为使用FS选项可以在命令行中指定域分隔符。
  640. $awk -F: '{print $0}' inputfile
  641. 使用awk脚本时,记住设置FS变量是在BEGIN部分。如果不这样做, awk将会发生混淆,不知道域分隔符是什么。
  642. 下述脚本指定FS变量。脚本从/etc/passwd文件中抽取第1和第5域,通过分号";"分隔passwd文件域。第1域是帐号名,第5域是帐号所有者。我举的例子是第七个域:
  643. [sam@Linux_chenwy sam]$awk -F: '{print $1,"\t",$7}' passwd
  644. root     /bin/bash
  645. bin      /sbin/nologin
  646. daemon   /sbin/nologin
  647. adm      /sbin/nologin
  648. lp       /sbin/nologin
  649. sync     /bin/sync
  650. ...
  651. 现使用脚本如下:
  652. [sam@Linux_chenwy sam]$cat passwd.awk
  653. #!/bin/awk -f
  654. #to call:passwd.awk /etc/passwd
  655. #print out the first and seventh fields
  656. BEGIN{
  657. FS=":"}
  658. {print $1,"\t",$7}
  659. 结果如下:
  660. [sam@Linux_chenwy sam]$chmod u+x passwd.awk
  661. [sam@Linux_chenwy sam]$./passwd.awk passwd
  662. root     /bin/bash
  663. bin      /sbin/nologin
  664. daemon   /sbin/nologin
  665. adm      /sbin/nologin
  666. lp       /sbin/nologin
  667. sync     /bin/sync
  668. ...
  669. 6. 向awk脚本传值
  670. 向awk脚本传值与向awk一行命令传值方式大体相同,格式为:
  671. awk script_file var=value input_file
  672. 下述脚本对比检查文件中域号和指定数字。这里使用了NF变量MAX,表示指定检查的域号,使用双引号将域分隔符括起来,即使它是一个空格。脚本如下:
  673. [sam@Linux_chenwy sam]$cat fieldcheck.awk
  674. #!/bin/awk -f
  675. #check on how many fields in a file
  676. #name:fieldcheck.awk
  677. #to call:fieldcheck MAX=n FS=<separator> filename
  678. #
  679. NF!=MAX{
  680. print("line" NR " does not have " MAX "fields")}
  681. 如果NF中的值不等于最大MAX值,则打印出"哪一行的域总数不是max"
  682. 如果以/etc/passwd作输入文件(passwd文件有7个域),运行上述脚本。参数格式如下:
  683. [sam@Linux_chenwy sam]$chmod u+x fieldcheck.awk
  684. [sam@Linux_chenwy sam]$./fieldcheck.awk MAX=7 FS=":" passwd
  685. 正好7个域,如果改成6,就会显示不同结果,试试看?
  686. 使用前面一行脚本的例子,将之转换成awk脚本如下:
  687. [sam@Linux_chenwy sam]$cat name.awk
  688. #!/bin/awk -f
  689. #name:age.awk
  690. #to call:age.awk AGE=n grade.txt
  691. #print ages that are lower than the age supplied on the comand line
  692. {if ($5<AGE)
  693. print $0}
  694. 文本包括了比实际命令更多的信息,没关系,仔细研读文本后,就可以精确知道其功能及如何调用它。
  695. 不要忘了增加脚本的可执行权限,然后将变量和赋值放在命令行脚本名字后、输入文件前执行。
  696. [sam@Linux_chenwy sam]$chmod u+x name.awk
  697. [sam@Linux_chenwy sam]$./name.awk AGE=10 grade.txt
  698. M.Tans 5/99 48311 Green 8 40 44
  699. J.Lulu 06/99 48317 green 9 24 26
  700. 同样可以使用前面提到的管道命令传值,下述awk脚本从du命令获得输入,并输出块和字节数。
  701. [root@Linux_chenwy sam]# cat duawk.awk
  702. #!/bin/awk -f
  703. #to call:du|duawk.awk
  704. #prints file/direc's in bytes and blocks
  705. BEGIN{
  706. OFS="\t";
  707. print "name" "\t\t","bytes","blocks\n"
  708. print "==============================="}
  709. {print $2,"\t\t",$1*512,$1}
  710. 使用du的结果如下
  711. [root@Linux_chenwy sam]# du
  712. 12      ./.kde/Autostart
  713. 16      ./.kde
  714. 8       ./.xemacs
  715. 4       ./sam
  716. 4       ./dir1
  717. 4       ./file6
  718. 184     .
  719. 执行:
  720. [root@Linux_chenwy sam]# du | ./duawk.awk
  721. name                    bytes   blocks
  722. ===============================
  723. ./.kde/Autostart                                6144    12
  724. ./.kde                          8192    16
  725. ./.xemacs                               4096    8
  726. ./sam                           2048    4
  727. ./dir1                          2048    4
  728. ./file6                         2048    4
  729. .                               94208   184
  730. 数组
  731. 前面讲述split函数时,提到怎样使用它将元素划分进一个数组。这里还有一个例子:
  732. [sam@Linux_chenwy sam]$awk 'BEGIN {print split("123#456#789",myarray,"#")}'
  733. 3
  734. 实际上m y a r r a y数组为
  735. Myarray[1]="123"
  736. Myarray[2]="456"
  737. Myarray[3]="789"
  738. 数组使用前,不必定义,也不必指定数组元素个数。经常使用循环来访问数组。下面是一种循环类型的基本结构:
  739. For (element in array ) print array[element]
  740. 对于记录"123#456#678",先使用split函数划分它,再使用循环打印各数组元素。操作脚本如下:
  741. [sam@Linux_chenwy sam]$cat arraytest.awk
  742. #!/bin/awk -f
  743. #name:arraytest.awk
  744. #prints out an array
  745. BEGIN{
  746. record="123#456#789";
  747. split(record,myarray,"#")}
  748. END{for (i in myarray) {print myarray[i]}}
  749. 要运行脚本,使用/dev/null作为输入文件。
  750. sam@Linux_chenwy sam]$chmod u+x arraytest.awk
  751. [sam@Linux_chenwy sam]$./arraytest.awk /dev/null
  752. 123
  753. 456
  754. 789
  755. 数组和记录
  756. 上面的例子讲述怎样通过split函数使用数组。也可以预先定义数组,并使用它与域进行比较测试,下面的例子中将使用更多的数组。
  757. 下面是从空手道数据库卸载的一部分数据,包含了学生级别及是否是成人或未成年人的信息,有两个域,分隔符为(#),文件如下:
  758. [sam@Linux_chenwy sam]$cat grade_student.txt
  759. Yellow#Junior
  760. Orange#Senior
  761. Yellor#Junior
  762. Purple#Junior
  763. Brown-2#Junior
  764. White#Senior
  765. Orange#Senior
  766. Red#Junior
  767. Red#Junior
  768. Brown-2#Senior
  769. Yellow#Senior
  770. Red#Junior
  771. Blue#Senior
  772. Green#Senior
  773. Purple#Junior
  774. White#Junior
  775. 脚本功能是读文件并输出下列信息。
  776. 1) 俱乐部中Yellow、Orange和Red级别的人各是多少。
  777. 2 ) 俱乐部中有多少成年人和未成年人。
  778. 查看文件,也许20秒内就会猜出答案,但是如果记录超过60个又怎么办呢?这不会很容易就看出来,必须使用awk脚本。首先看看awk脚本,然后做进一步讲解。
  779. [sam@Linux_chenwy sam]$cat belts.awk
  780. #!/bin/awk -f
  781. #name:belts.awk
  782. #to call:belts.awk grade2.txt
  783. #loops through the grade2.txt file and counts how many
  784. #belts we have in (yellow,orange,red)
  785. #also count how many adults and juniors we have
  786. #
  787. #start of BEGIN
  788. #set FS and load the arrays with our values
  789. #BEGIN部分设置FS为符号#,即域分隔符
  790. BEGIN{FS="#"
  791. #Load the belt colours we are interested in only
  792. #因为要查找Yellow、Orange和Red三个级别。
  793. #然后在脚本中手工建立数组下标对学生做同样的操作。
  794. #注意,脚本到此只有下标或元素,并没有给数组名本身加任何注释。
  795. belt["Yellow"]
  796. belt["Orange"]
  797. belt["Red"]
  798. #end of BEGIN
  799. #load the student type
  800. student["Junior"]
  801. student["Senior"]
  802. }
  803. ##初始化完成后, BEGIN部分结束。记住BEGIN部分并没有文件处理操作。
  804. #loop thru array that holds the belt colours against field-1
  805. #if we have a match,keep a running total
  806. #现在可以处理文件了。
  807. #首先给数组命名为color,使用循环语句测试域1级别列是否
  808. #等于数组元素之一(Yellow、Orange或Red),
  809. #如果匹配,依照匹配元素将运行总数保存进数组。
  810. {for (colour in belt)
  811. {if($1==colour)
  812. belt[colour]++}}
  813. #loop thru array that holds the student type against
  814. #field-2 if we have a match,keep a runing total
  815. #同样处理数组'Senior_or_junior',
  816. #浏览域2时匹配操作满足,运行总数存入junior或senior的匹配数组元素。
  817. {for (senior_or_junior in student)
  818. {if ($2==senior_or_junior)
  819. student[senior_or_junior]++}}
  820. #finished processing so print out the matches..for each array
  821. #END部分打印浏览结果,对每一个数组使用循环语句并打印它。
  822. END{for (colour in belt )print "The club has ",belt[colour],colour,"Belts"
  823. #注意在打印语句末尾有一个\符号,用来通知awk(或相关脚本)命令持续到下一行,
  824. #当输入一个很长的命令,并且想分行输入时可使用这种方法。
  825. for (senior_or_junior in student) print "The club has ",\
  826. student[senior_or_junior],senior_or_junior,"student"}
  827. 运行脚本前记住要加入可执行权限
  828. [sam@Linux_chenwy sam]$chmod u+x belts.awk
  829. [sam@Linux_chenwy sam]$./belts.awk grade_student.txt
  830. The club has  3 Red Belts
  831. The club has  2 Orange Belts
  832. The club has  2 Yellow Belts
  833. The club has  7 Senior student
  834. The club has  9 Junior student
复制代码

原文地址:http://bbs.chinaunix.net/thread-448687-1-1.html
作者: wsk170    时间: 2009-10-17 16:57

对于文本处理awk(gawk)是目前最强大的工具
感谢楼主整理,收益良多啊
作者: wc726842270    时间: 2010-9-15 00:12

真是不错的东西。只不过目前对我来说这东西还不是时候。先收藏吧




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2