说明
刚接触unix相关的书籍的时候,里面有一章是专门介绍awk的使用的。但当时觉得它过于复杂,很难记住它是什么,能干什么。想来是因为我知识的储备还不够。Awk主要的思想就是,它的确是一种设计得非常精巧的编程语言,很好地满足了实际应用的同时,也能够让我们以软件工程,编译原理等理论性的东西欣赏它。因此,我就知道awk的使用不再是一件困难的事情。
awk基础
有些东西,理解它的过程,就好像是要进入一个豪华的庭院,要进入门内,非得绕过门前的屏风墙。不这样,就看不到里面的风景。
awk本身的语法是比较怪异的。然而外面的人看到的,往往是用awk写出来的程序,而非程序的规范。所以就不得要领。人们又常常抱着学习awk就直接是为了处理文本的功利目的,一旦不能一眼看出来它在处理文本上的直观,就放弃了。但是,绝大部分事情,是不能凭感官的。我们必须要有一种理论的指导。
让我们忽略掉awk的语法形式,单纯地考虑,如果是我们,将会怎样处理文本,特别是在C语言的框架下编写这样的程序。将能设想到,基本的方法,还是通过传递变量。也就是至多每读取一行,就自动将参数匹配到指定的位置。因此整个awk程序,首先看成是一个函数。宿主环境代理地读取目标文本,并在每次调用函数的时候得到参数。文本处理的思想,也就只能如此了。
另外,我们看到,awk在文本处理上做得更深一步。它本身是一个编译型的语言。也就是说,在文本没有读取之前,宿主环境先进行语法等方面的检查,通过之后才开始处理程序。这一点与 make 程序相似。因此,首先应当考虑到awk是如何检查脚本的,再考虑脚本如何执行文件。命令
awk -f AWK脚本.awk 数据文件
可以与
python py脚本.py 参数
相类比。
通常调用awk的时候,AWK脚本可以是即时编辑的,也就是脚本的内容直接出现在命令行的第一个参数中。把这个功能看成是一个语法糖,也许就顺畅多了。
awk程序的执行流程是:
- 读取指定的脚本命令并进行语法的检查;
- 从指定的数据文件中读取一个数据行;
- 自动更新相关的内建变量的值,如
$0,$1,NR; - 依次执行程序中每个
Pattern { Actions }指令; - 处理完当前行之后,读取下一个数据行,自动返回步骤三继续执行;
- 文件处理完毕,结束运行。
Awk主要概念
Awk程序的主要语法是 Pattern { Actions } 。一个Awk程序常常由许多这样结构的语句构成。在执行的时候, Pattern 主要是确定一个逻辑表达式,若数据行符合这样的条件,就执行相应的 Actions ;不符合条件则执行下一个 Pattern { Actions } 语句。如果 Pattern 为空,则表示无条件执行该语句的 Actions 。
在 Pattern 部分中,可能最需要介绍的部分是Awk的关系运算符。Awk本身是用C语言编写的,而且继承了其中常见的关系运算符,如 > 、 < 、 >= 、 <= 、 == 、 != 。此外,Awk还用运算符与!表示字符串与正则表达式的匹配。 BEGIN 与 END 是特殊定义的模式匹配语句,分别表示在第一个记录之前,与最后一个记录之后。
在 Actions 部分,主要是许多Awk指令。Awk的指令与C语言也十分相似,大多数时候,只是改用Shell那样的传参方式,而非C语言的函数。Awk的指令大致可分为普通指令与控制指令两类。前者如 print , getline ;后者如 if(...){...} else{...} , while(...){...}... 。除此之外,C语言赋值表达式往往也继续有效。
遇到 Pattern { Actions } 语句,Awk会先计算 Pattern 的值,若计算出的值为 true 、非零的数字或者非空字符串,则awk将执行后面的 Actions 。在其它条件下,将跳过相应的 Actions 。这一过程,可以与路由器上的ACL处理流程相类比。
为了方便对数据行的处理,Awk提供了许多内建的变量。常见的变量如下:
| 变量符号 | 含义 |
|---|---|
| $0 | awk当前所读入数据行的所有内容 |
| $1 | 当前所读入数据行的第一个字段的内容,下个字段的变量依次类推 |
| $NF | Number of Fields。表示当前读入数据行 $0 含有的字段数 |
| $NR | Number of Records。表示已读入的数据行的数目 |
| $FILENAME | 当前文件的名称 |
注:awk的变量并不需要用$符号括住,用以字母打头的英文字符即可。$实际上是awk的一元运算符,它以一个数字为参数,表示操作“取字段”。
awk处理数据的时候,自动从数据文件中每次读取一行记录,然后将记录切分成若干个字段,程序可使用$1、$2等直接取得字段中的内容。若要简单打印出相应字段,使用print $1, $2这样的语句即可。
此外,awk还提供 printf() 指令,它的使用方式与C语言更相近,用于格式化字符串。
比如:
awk '
{ printf("%d times %d is %d.\n", $1, $2 ,$1 * $2) }
'
234234234 234234234
将显示出两个字段的乘积。从上面的例子我们也看出,awk的整型运算功能是非常强大的,可以进行非常大的整数之间的运算。可以认为具有无限的精度。
作为一个领域专用的语言,在变量的使用上,要尽可能足够简单。所以awk的变量,不需要宣告就能够使用,类型是自动确定。并且,awk提供的“字典”(有人称为数组)类型,也可以自动使用。引用字典不存在的键值的时候,该键值会自动被创建。
awk与shell指令
在awk中使用shell指令是比较方便的。其思想是让字符串通过管道运算符|与awk内置的函数运算。字符串被当成指令执行,执行后的输出的结果,被当成内置函数的运算符。比如
"who" \| getline
getline 是awk的输入指令。当 Pattern 为 BEGIN 或 END 的时候, getline 默认从 stdin 读取数据,否则从awk正处理的数据文件上读取数据。 若是后者,将会使数据文件中的两行当成一个记录来处理。
使用getline之后,变量 $0 , $1 等的内容会被刷新。
在awk中,输出文件则使用重定向运算符 > 或 >> 。它可以用在任意 Action 之后。重定向运算符可以理解成一个普通的 Action 与一个字符串运算,而字符串被解释成文件的路径。
awk也提供了一个 system() 函数以调用shell命令。
注意,在执行shell指令的时候,相同的字符串代表同一个pipe名称,因此尽管出现在不同的 Actions 当中,shell命令可能只启动一次。具体意思是,比如 print $1, $2 > "sort -k 1" 这一语句,并非每执行一个 print ,就启动一个 sort -k 1 进程。而是所有 print 出来的内容,都送到 sort -k 1 这个程序的输入管道中,因而所有 print 出来的内容都输入同一文件流中,被 sort -k 1 处理。要想关闭此管道,必须显式地使用 close() 函数。
awk程序的结构
一般而言,awk当中都是 Pattern { Actions } 指令。但是还有其它类型的语句,包括注释与函数定义。注释以#号开头的行,而函数字义与C语言类似,比如 function double(x) {return 2*x} 。
其它
在写awk程序的时候,可能会用到许多字符串函数,如 substr() , length() 。一般而言,是不可缺少的。
awk读取文件的时候,使用FS作为字段分割符。它可以取一个正则表达式, 但默认情况下都是空白字符。若不想在启动awk时指定 FS ,可以在 BEGIN 对应的 Actions 中更改 FS 的值。
awk读取文件的时候,使用 RS 作为记录分割符。默认 RS 是字符 \n 。但是我们也可以改成其它的符号。特别地,当定义 RS="" 时,分割符是空白的行。此时,中间无论多少空白行,都被当成是记录分割符。
awk程序也可以有参数。这些参数在程序中通过 ARGV[] 与 ARGC 标识。
awk中所有的变量都默认是全局变量,除了函数参数之外。因此在写递归程序的时候,有一些编程上的技巧。