awk oneliner一共分为五部分(该部分为作者在前四部分发表后的一年以后做的一个总结),而国内的译文大多也只到第四部分。我也到作者的博客上去看过所有的原文,显然如果前四部分都完全掌握了,第五部分看起来只是一个小case。

1、创建一个固定长度的字符串

1awk 'BEGIN { while (a++<513) s=s "x"; print s }'

这个段程序用BEGIN这个特殊的匹配模式让后面的代码在awk试图读入任何东西前就执行。在里面是一个被执行了513次的循环,每次循环中“x”都被添加到变量s的最后。循环结束后,s的内容被输出。因为这段代码只有这一句,所以awk在执行完BEGIN模式语句后就退出了。这段循环代码不仅仅可用在BEGIN中,你可以在awk的任何代码段里面使用,包括END。

很不幸这段代码不是最有效率的,这是一个线性的解决方案。Awk Tips, Tricks and Pitfalls的作者 waldner 有一个非线性的解决方案:

1function rep(str, num,     remain, result) {
2    if (num < 2) {
3        remain = (num == 1)
4    } else {
5        remain = (num % 2 == 1)
6            result = rep(str, (num - remain) / 2)
7    }
8    return result result (remain ? str  : "")
9}

该函数同样用到三元运算a?b:c,而定义的该函数的内部又不停的调用函数本身,达到循环的目的。而前面的if……else语句主要用业说明remain是一个奇数。而通过下面的语句调用该函数:

1awk 'BEGIN { s = rep("x", 513) }'

以达到上面那段不高效的但确十分简洁的语句一样的效果。

2、在某个位置插入指定长度的字符串

1gawk --re-interval 'BEGIN{ while(a++<49) s=s "x" }; { sub(/^.{6}/,"&" s) }; 1'

这段代码只能在gawk下使用,因为它用到了interval expression,即这里的{6},作用是让前一个字符.匹配多次。.{6}便可以匹配6个任意字符。gawk使用interval expression需要用到参数-re-interval。

同前一例一样,首先在BEGIN段里面,建立了一个49个字符长的字符串放在变量s里。接下来是对每一行,进行替换,&这里代表的是匹配的字符串部分,所以sub的结果是将每一行第7个字符开始的内容替换成了s。然后是逐行输出。如果不是gawk,需要这样写

1awk 'BEGIN{ while(a++<49) s=s "x" }; { sub(/^....../,"&" s) }; 1'

其输出结果为从每行的第六个字符开始,连续输出49个x,然后再接着输出原来的行第七个字符及其以后的内容。如果每行不足七个字符时,该行输出的值不变。

注意:上面语句中的点并不是点本身的意思,而是代码任意一个字符,有点类似于bash脚本中用到字段?所代表的意思。

3、利用字符串建立数组

1split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", month, " ")

其初,我一直不能白作者为什么写上面的例子,而显然在bash shell中split也没有这个用法。而在php的split语句中我倒是常见到该函数的使用上面的有法。显然作者意思已经很明了,该例中作者创建了一个array,其中month[1]=Jan、month[2]=Feb,后面依次类推。(而后面将会讲到awk也具有类似的用法。)

建立用字符串做编号的数组(类似Ruby的Hash,Python的dict)

1for (i=1; i<=12; i++) mdigit[month[i]] = i

不多做解释,只是用来类比。显然bash中不会存在这样的用法。输出第5个字段为abc123的行

1awk '$5 == "abc123"'
2等价于awk '$5 == "abc123" {print $0}'

输出第5个字段不为abc123的行

1awk '$5 != "abc123"'
2等价于awk '$5 != "abc123" {print $0}'
3或awk '!($5 == "abc123")'

输出第7字段匹配某个正则表达式的行

1awk '$7  ~ /^[a-f]/'

这里用了~来进行正则表达式的匹配哦。如果你要不匹配的行,可以

1awk '$7 !~ /^[a-f]/'