2 概述
AWK于1970年代诞生于贝尔实验室, 它是一门被设计来处理基于文本的数据—包括文件或者数据流—的通用编程语言.我注意到Erik Wendelin写过一篇文章"awk is a beautiful tool". 在这篇文章中他说到, 介绍awk的最好方式是实例. 对此我非常同意.
Eric Pement的AWK单行程序合集包含五小节:
1.文件间距
2.计数和计算
3.文本转换和替换
4.特定行的输出
5.特定行的删除
本文的第一部分解释前两个小节:"文件间距"和"计数与计算". 第二部分介绍"文本转换和替换", 最后一部分解释"特定行的输出/删除".这些简短的例子能够工作于所有版本的awk, 包括nawk(AT&T's new awk), gawk(GNU's awk), mawk(Michael Brennan's awk), 以及oawk(old awk).
让我们开始吧!
3 行间距
3.1 行间距加倍
awk '1; { print "" }' filname.ext
它是如何工作的呢? 这一行就是一个awk程序, 每个awk程序都包含一系列的pattern-action语句"pattern {action statements}". 在这个例子中, 一共有两个语句:"1"和"{print ""}". 在pattern-action语句中, 模式或者动作都可以省略掉. 如果省略模式, 动作会应用于输入中的每一行. 而省略掉的动作等价于'{print }'. 因此, 这行程序可以转换为:
awk '1 { print } { print "" }' filname.ext
动作只会应用于匹配模式的行, 也就是说, 模式为真(true). 由于'1'永远为真, 因此这行程序可以进一步转换为两个print语句:
awk '{ print } { print "" }' filname.ext
Awk中的每个print语句都会跟一个ORS-输出记录分隔符(Out Record Separator)变量, 默认情况下是一个换行符. 第一个不带参数的print语句等价与"print $0", 这里$0表示输入的整行内容.第二个print语句什么都不输出, 但是由于每个后面都有一个ORS, 因此它实际输出一个换行. 好了, 现在每一行都变成双倍间距了.
3.2 另一种行间距加倍的方式
awk 'BEGIN { ORS="\n\n" }; 1' filname.ext
BEGIN是一个特殊的模式, 它不是用来匹配输入. 而是在任何输入被读入之前执行. 这行程序通过将ORS设定为两个换行来使文件间隔加倍. 就像我在前面提到过的, 语句"1"会被转换为"{print}", 并且每个print语句都会以ORS变量作为结尾.
3.3 使文件行间距加倍, 并且两行文本之间不会有多于一行的空行.
awk 'NF { print $0 "\n" }' filname.ext
这个单行程序使用另一个特殊的变量:NF-Number of Fields. 它保存当前行被分割后的字段数. 例如, "this is a test"被分割为4部分, 因此NF的值为4. 空行""不能被分割为任何片段, 这时NF的值为0. 使用NF作为模式可以有效的过滤空行. 这个单行程序的意思是:如果包含任意数目的字段, 则将整行内容输出, 然后再输出一个换行.
3.4 使行间距增加两倍.
awk '1; { print "\n" }' filname.ext
这个单行程序跟上一个很类似. '1'被转换为'{ print }', 转换后的结果为:
awk '{ print; print "\n" }' filname.ext
它输出一行的内容, 然后输出一个换行, 后面再跟一个ORS, 默认情况下ORS也是一个换行.
4 计数和计算
4.1 单独为每个文件的行计数.
awk '{ print FNR "\t" $0 }' filname.ext
这个Awk程序在每行前面添加FNR–File Line Number–和一个tab(\t).FNR变量单独保存每个文件的当前行行号. 例如, 如果用这个单行程序处理两个文件,其中一个有10行,另一个12行, 那么它会从1到10 给第一个文件的各行编号, 然后重新从1开始, 将第二个文件的各行依次编号为1到12. 也就是说在处理两个文件中间, FNR会被重置.
4.2 给所有文件的行计数.
awk '{ print NR "\t" $0 }' filname.ext
这个程序跟上一个的唯一不同在于:这儿用的是NR(Line Number)变量, 它在文件之间不会被重置. 它会为输入的所有行计数. 例如, 如果用这个程序来处理上个例子中的两个文件, 它会将文件中的行依次编号为1到22(10+12).
4.3 用精心设计的方式计数.
awk '{ printf("%5d : %s\n", NR, $0) }' filname.ext
这个单行程序使用printf()函数来以定制的格式计数. 它就像我们常见的printf()函数一样带有格式参数. 注意printf()输出的时候并不会附加ORS, 因此我们必须显式地输出换行符(\n). 这个程序以右对齐的方式输出行号, 后面跟一个空格和分号, 然后是输入行.
4.4 只对文件中的非空行计数.
awk 'NF { $0=++a " :" $0 }; { print }' filname.ext
Awk变量是动态的, 它们产生于第一次被使用的时候. 这个单行程序中, 当输入行非空的时候, 先将a加1, 然后将a的值添加到当前行的前面一起输出.
4.5 计算文件行数(模拟wc -l)
awk 'END { print NR }' filname.ext
END是另一个不会匹配输入行的模式. 它在所有输入都处理完之后执行. 这个单行程序在处理完所有输入之后输出NR的值. NR中保存处理的总行数.
4.6 输出每行中各个字段的和.
awk '{ s = 0; for (i = 1; i <= NF; i++) s = s+$i; print s }' filname.ext
Awk有一些C语言的特征, 例如for(;;) {…}循环. 这个单行程序循环处理输入行的各个字段(一行中有NF个字段), 将各个字段的值加到变量's'. 然后将累计值s输出, 接着再处理下一行.
4.7 输出所有行中各个字段的和.
awk '{ for (i = 1; i <= NF; i++) s = s+$i }; END { print s }' filname.ext
这个程序跟4.6中的差不多, 区别在于它输出所有字段的和. 要注意它是怎样没有将变量's'初始化为0. 由于变量是动态产生的, 因此不需要初始化.
4.8 将每个字段替换为它的绝对值.
awk '{ for (i = 1; i <= NF; i++) if ($i < 0) $i = -$i; print }' filname.ext
这个单行程序使用了两个其他的C语言特性, 也就是if(…){…}语句和省略花括号. 它循环处理一行输入的所有字段, 检查是否有字段的值小于0. 如果有字段的值小于0, 就对其取反, 是字段值变为正数. 字段可以通过一个变量间接的访问. 例如, i=5; $i='hello', 将第五个字段的值置为'hello'.下面是重写过的程序, 跟上面的相同, 不过为了清晰起见添加了花括号. 在每行输入的所有字段都被它们的绝对值替换之后, 'print'语句就会执行.
awk '{for (i = 1; i <= NF; i++) {if ($i < 0) {$i = -$i;}}print}' filname.ext
4.9 记录输入文件中所有字段的数目.
awk '{ total = total + NF }; END { print total }' filname.ext
这个单行程序匹配所有的输入行, 并且将每行中的字段数加起来. 已处理过的输入的字段数会保存在变量total中. 一旦输入被处理完, 特殊的模式'END{…}'就会被执行, 它会输出总的字段数.
5 特定行的删除/输出
5.1 输出包含单词"Beth"的行的总行数.
awk 'Beth { n++ }; END { print n+0 }' filname.ext
这个单行程序有两个pattern-action语句. 第一个是'Beth { n++ }'. 在两个斜线之间的模式是一个正则表达式. 它匹配所有包含模式"Beth"的行(不一定非得是单词"Beth", 也可以是"Bethe"或者"theBeth333"). 当某行输入匹配的时候,变量'n'增加1. 第二个pattern-action语句是'END {print n+0}'. 它在文件处理完之后执行. 注意语句'print n+0'中的'+0'. 在没有任何输入行匹配的情况下, 它会强制输出'0'(此时'n'是未定义的). 假设没有'+0', 那就会输出一个空行.
5.2 查找第一个字段的值(数字)最大的行.
awk '$1 > max { max=$1; maxline=$0 }; END { print max, maxline }' filname.ext
这个单行程序始终保存输入行中第一个字段的最大值(在变量max中)以及相应的行(在变量maxline中). 一旦处理完所有的行, 就将最大值和对应行输出.
5.3 输出每行中的字段数, 以及对应的行.
awk '{ print NF ":" $0 } ' filname.ext
这个单行程序仅仅输出预定义的变量NF-Number of Fields, 它保存输入行中的字段数, 然后输出分号和当前行.
5.4 输出每行的最后一个字段.
awk '{ print $NF }' filname.ext
Awk中的字段不一定非得通过常量引用. 例如, 类似'f=3; print $f'的代码会输出第三个字段. 这个单行程序输出第NF个字段. $NF就是一行中的最后一个字段.
5.5 输出最后一行的最后一个字段.
awk '{ field = $NF }; END { print field }' filname.ext
这个单行程序始终将最后一个字段的值保存在变量'field'中. 一旦处理完所有的行, 变量field中保存的就是最后一行的最后一个字段, 然后会将它输出.
5.6 输出字段数多于4的行.
awk 'NF > 4' filname.ext
这个单行程序省略了action语句. 就像我在3.1中提到的, 省略的action语句等价于'{print}'.
5.7 输出最后一个字段的值大于4的行.
awk '$NF > 4' filname.ext
这个单行程序跟5.4类似. 它通过NF引用最后一个字段. 如果它大于4, 则将它输出.
Date: 2008/12/10 11:09:39