调试全局变量上面谈到了变量污染发生的条件以及解决方案。这里再补充一下出现相关问题时的一个简单调试方法,那就是 awk 的 -dump-variables 参数,它可以把程序运行结束过后的所有全局变量打印到一个文本文件提供调试。我们来看下面的例子,对 fac1-3.awk 执行的输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| [robert@saphires awk_var]$ echo "" | awk -f fac1-3.awk --dump-variables=/tmp/var.dump
fac(1) = 1
fac(2) = 2
fac(3) = 6
fac(4) = 24
fac(5) = 120
fac(6) = 720
fac(7) = 5040
fac(8) = 40320
fac(9) = 362880
fac(10) = 3628800
[robert@saphires awk_var]$ cat /tmp/var.dump
ARGC: number (1)
ARGIND: number (0)
ARGV: array, 1 elements
BINMODE: number (0)
CONVFMT: string ("%.6g")
ERRNO: number (0)
FIELDWIDTHS: string ("")
FILENAME: string ("-")
FNR: number (1)
FS: string (" ")
IGNORECASE: number (0)
LINT: number (0)
NF: number (0)
NR: number (1)
OFMT: string ("%.6g")
OFS: string (" ")
ORS: string ("\n")
RLENGTH: number (0)
RS: string ("\n")
RSTART: number (0)
RT: string ("")
SUBSEP: string ("\034")
TEXTDOMAIN: string ("messages")
i: number (11)
s: number (3628800)
value: number (3628800)
|
我们可以看到,s 这个在 factorial() 函数中变量其实是被当成了一个全局变量,虽然 fac1-3.awk 的运行结果已经完全符合我们的功能需要,但是如果把这个叫 factorial() 的函数移植到另外一个不是我们写的代码里面去,天知道会不会与其它一个叫 s 的全局变量混用引起变量污染呢?如果那一天真的到来的话,也许我们要花上好几个小时才能找出这样的污染根源。如何在第一时间避免这样的灾难发生呢?当然是把 s 也定义成局部变量,示例如下:
清单 5. fac1-4.awk 最终版本1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| 1 #
2 # fac1-4.awk
3 # version 0.4 of fac1.awk
4 #
5
6 function factorial(n, _ARGVEND_, i, s)
7 {
8 s=1;
9
10 for (i=1; i<=n; i++)
11 {
12 s *= i;
13 }
14
15 return s;
16 }
17
18 {
19 for (i=1; i<=10; i++)
20 {
21 value = factorial(i);
22 printf("fac(%d) = %d\n", i, value);
23 }
24 }
25
|
然后我们再看一下 fac1-4.awk 的 vardump :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| [robert@saphires awk_var]$ echo "" | awk -f fac1-4.awk --dump-variables=/tmp/var.dump
fac(1) = 1
fac(2) = 2
fac(3) = 6
fac(4) = 24
fac(5) = 120
fac(6) = 720
fac(7) = 5040
fac(8) = 40320
fac(9) = 362880
fac(10) = 3628800
[robert@saphires awk_var]$ cat /tmp/var.dump
......(omitted)
i: number (11)
value: number (3628800)
|
从上面的结果可以看到,在程序运行结束过后,只有两个我们觉得应该有的全局变量 i 和 value 存在。这样, factorial() 函数基本上可以作为一个通用函数被移植到其它的 Awk 脚本中去了。通过对 Awk 全局变量的调试输出,我们也得出一个书写 Awk 函数需要注意的原则:那就是只要是局部变量,都应该在参数列表中进行定义,只有这样才能完全避免全局变量污染发生。
使用包含文件上面的 fac1-4.awk 中的 factorial() 函数已经可以视为安全的 Awk 函数了。在 Awk 的应用中,是否可以象C语言的#include或bash中的source一样包含其它源文件呢?答案是可以的。让我们先把 factorial() 函数单独存到 fac-lib.awk 中:
清单 6. fac-lib.awk Awk 函数库1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| 1 #
2 # library for awk
3 #
4
5 function factorial(n, _ARGVEND_, i, s)
6 {
7 s=1;
8
9 for (i=1; i<=n; i++)
10 {
11 s *= i;
12 }
13
14 return s;
15 }
16
|
- 一种方法是通过引用多个 awk 脚本来实现,这种方式不需要有任何包含源文件相关的标志:
清单 7. fac3.awk 不包含 Awk 函数库的主程序
1
2
3
4
5
6
7
8
9
10
11
12
13
| 1 #
2 # fac3.awk
3 # original version of fac3.awk
4 #
5
6 {
7 for (i=1; i<=10; i++)
8 {
9 value = factorial(i);
10 printf("fac(%d) = %d\n", i, value);
11 }
12 }
13
|
运行并查看结果:
1
2
3
4
5
6
7
8
9
10
11
| [robert@saphires awk_var]$ echo "" | awk -f fac-lib.awk -f fac3.awk
fac(1) = 1
fac(2) = 2
fac(3) = 6
fac(4) = 24
fac(5) = 120
fac(6) = 720
fac(7) = 5040
fac(8) = 40320
fac(9) = 362880
fac(10) = 3628800
|
下面这种方式则更象其它语言中的源代码包含,但是对于有函数包含的 Awk 脚本,我们需要用 igawk 来执行。igawk 实际上也只是一个脚本,它在运行时分析 Awk 脚本中的 @include 标志,并把 @include 包含的文件合并到当前脚本的 @include 行,然后进行解释执行。一方面,Awk 所谓的包含功能也这么的不地道,另一方面,这个包含功能的扩展本身也是由 Awk 完成的,非常有趣。下面来看一下利用 igawk 运行包含函数库的 Awk 脚本。
清单 8. fac3-2.awk 包含 Awk 函数库的主程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| 1 #
2 # fac3-2.awk
3 # original version of fac3-2.awk
4 #
5
6 @include fac-lib.awk
7
8 {
9 for (i=1; i<=10; i++)
10 {
11 value = factorial(i);
12 printf("fac(%d) = %d\n", i, value);
13 }
14 }
15
|
运行并查看结果:
1
2
3
4
5
6
7
8
9
10
11
| [robert@saphires awk_var]$ echo "" | igawk -f fac3-2.awk
fac(1) = 1
fac(2) = 2
fac(3) = 6
fac(4) = 24
fac(5) = 120
fac(6) = 720
fac(7) = 5040
fac(8) = 40320
fac(9) = 362880
fac(10) = 3628800
|
|