AWK语言第二版 2.2选择

2.2 选择

Awk的基本结构组成,就是用一系列的样式选择出感兴趣的行,并对其进行操作。这样的程序很多都是一次性的,敲出来之后就用几次。然而,有些程序很有用,如果每次用之前都要重新敲一遍代码,就太麻烦了,所以值得将它们存到脚本里,以便有需要随时使用。

下面有些例子,与现有的Unix工具功能上是重复的,之所以把它们放在这里讨论,更多地是为了讲解Awk,而不是要取代这些工具,当然Awk的灵活性意味着你可以定制自己所需的特殊版本。(而且你能用Awk做出在多个Unix发行版之间可移植的版本,当这些工具在不同Unix发行版间存在差异的时候)。比如,Unix命令 head 默认会打出前10行,这与另一个简单的 sed 命令 10q 是等价的。下面的一行Awk程序也有同样的功能:

NR <= 10

然而,如果输入量特别大,上面的程序就很低效,因为它会读入所有输入,但在第十行之后就啥都不干了。改进后的版本会输出所有行,但在第十行之后退出:

{ print }
NR > 10 { exit }

原始版本的运行时间与输入长度成正比,而改进后的版本只会用固定很短的时间。

如果你只想打印输入的前三行和后三行该怎么做呢?(假如你有个按出现次数排序的列表,你只想看出现最多和最少的值。) 一个简单的办法是将所有输入都保存下来,只打印所需的行:

awk '{ line[NR] = $0 }
END { for (i = 1; i <= 3; ++i) print line[i]
     print "..."
     for (i = NR -2; i <= NR; ++i) print line[i]
    } ' $*

如果输入行小于7行,这个程序会有问题,但作为个人使用的工具,只要你使用时记得它的限制,也许就没啥大不了的。

虽然只打印一小部分,但这个程序会将所有输入都保存下来,因此输入为大文件时可能就会很慢。还有一个办法,就是先把前三行打出来,然后永远只保存最近的三行,在结束时打出来:

awk 'NR <= 3 { print; next }
     { line[1] = line[2]; line[2] = line[3]; line[3] = $0 }
 END { print "..." 
      for (i = 1; i <= 3; ++i) print line[i] } ' $*

next 语句会结束当前记录(当前行)的处理,并重新回到程序开头,处理下一行。(注:可以将 next 类比为C语言的continue,即跳过下面的语句,重新开始循环——在Awk里是重新读入一行。在这个例子里面,next跳过的就是第二行的代码

不过令人诧异的是,这个版本比前一个版本还慢了大约三分之一,估计是因为做了太多的行拷贝操作。第三个办法,是将输入作为环形缓冲区:只保留三行,而且仅对最后三行做索引的轮转(1变2,2变3,3变1),这样就不会有多余的拷贝操作了:

awk ' NR <= 3 { print; next }
     { line[NR%3] = $0 }
END { print "..."
     i = (NR+1) % 3
     for (j = 0; j < 3; ++j) {
        print line[i]
        i = (i+1) % 3
     } 
} ' $*

经过实测,这个版本也仅比第一个版本快了一点点,而这微小的提升,代价是END块里面有些复杂的索引处理。为此增加的复杂度是不值的,因为这样的程序处理一百万行的输入文件,也就只要几秒钟。

是实时处理输入,还是将输入保存到数组中最后用END来处理,两者之间需要权衡。幸运的是,现代的处理器够快,内存够大,因此通常都是从尽可能简单的代码开始写起,而不是一开始就试图去节省cpu时间或内存空间。当然,这也是我们处理问题的方式:先用简单快速的方法,除非必要,后面再用更复杂的方法。

有时也要丢弃前面n行,并打印剩余的行;比如有个带表头的文件,而你需要跳过表头来处理表内的数据:

awk ' NR > 1'

前面说过Unix有个工具叫 head,对应地还有个 tail,就是用来输出文件的最后n行。但如果你要将最后n行倒序输出呢?这时你自己写的工具就派上用场了,因为并不是所有的版本都会提供你需要的选项。下面是个简单实现的版本:

awk '{ line[NR] = $0 }
 END { for (i = NR; i > NR-3; i--) print line[i] } ' $*

如果把上面的 i > NR -3 替换成 i > 0,就能对整个文件进行倒序输出。

练习2-1:修改“打印前几行后几行”的程序,使之能正确处理更短的输入。

练习2-2:改进最后一个样例,支持把需要打印的行数作为参数

你可能感兴趣的:(AWK,linux,开发语言)