本来计划年前写完的,结果现在才搞定,手册的内容一边翻一边实验,还有awk版本问题,又参考Effective AWK Programming对语法和示例做了些补充。终于写完了,大家元宵节快乐!
AWK简介
AWK是一门解释型的编程语言,它的名字来源于它的三位作者的姓氏:Alfred Aho,Peter Weinberger和Brian Kernighan。AWK能够应用于广泛的计算和数据处理任务。所有的GNU/Linux发行版都自带GAWK,即GNU AWK,是AWK的扩展并且与AWK完全兼容。
和
上一篇文章
讲到的sed命令类似,AWK逐行读取输入流中的内容,并对读取的行执行所有命令,如此循环,直到输入流结束。
本文基于GAWK进行介绍,因为GAWK比原生AWK使用更普遍些。
程序结构
AWK命令格式如下:
awk [options] ‘program’ input-file1 input-file2 …
或者
awk [options] -f program-file input-file1 input-file2 …
第一种格式中,awk从file中获取输入流,然后执行
单引号
内的程序。第二种格式则是从文件
program-file
中获取将要执行的程序。
上述AWK命令的program部分的结构可分为三块:BEGIN、BODY和END。
BEGIN:在AWK命令的一开始执行的动作,它只执行一次,可以把变量初始化放在这里。注意,BEGIN部分是可选的,并且一个AWK命令中可以有多个BEGIN块。另外,如果有-v选项的赋值操作,则-v的操作在BEGIN之前。
BEGIN块的写法为:
BEGIN{…}
BODY:程序主体,对输入流的每一行执行动作。如果存在BEGIN或END,则这部分是可选的。一个AWK命令中可以有多个BODY块。
BODY块的写法为:
{…}
END:在AWK命令的最后执行的动作,它只执行一次。注意,END部分是可选的,并且一个AWK命令中可以有多个END块。另外,使用END { }块时,awk的file参数不能省略。
END块的写法为:
END{…}
例如下面的命令:
[root@ubuntu]awk_test:$ awk 'BEGIN{print "Output start!"}; {print}; END{print "Output done!"}' awk_test.txt
Output start!
USER PID %MEM VSZ RSS STAT START TIME COMMAND
root 1 0.1 3652 1916 Ss Jan07 0:03 /sbin/init
root 2 0.0 0 0 S Jan07 0:00 [kthreadd]
root 3 0.0 0 0 S Jan07 0:02 [ksoftirqd/0]
root 26 0.0 0 0 S Jan07 0:40 [kswapd0]
user 495 0.1 3588 1092 Ss Jan07 0:00 /sbin/udevd --daemon
user 860 0.0 3584 908 S Jan07 0:01 /sbin/udevd --daemon
user 1137 0.0 4520 776 S Jan07 0:00 smbd -F
user 1550 0.1 4521 1816 Ss Jan07 0:15 nmbd -D
Output done!
可以看到这条命令在命令的开始打印一行输出“Output start!”,然后对文件中的每一行内容执行print操作,最后又打印出一行输出“Output done!”。
从句法结构上来讲,program由一条条规则组成,每条规则由
模式
和
动作
组成,即模式匹配后执行相应的动作。动作放在花括号内以与模式区分。所以,program一般的格式是这样的:
pattern
{
action
}
pattern
{
action
}
…
那么,下面这条命令(打印长度大于80字符的行):
awk 'length($0) > 80' data
可以看到这条awk命令只有pattern而没有action部分。如果没有action部分,则执行
默认动作
:打印整个record。
也可以把program写到文件里(不用加单引号),通过AWK的第二种格式来执行。
[root@ubuntu]awk_test:$ cat progfile
BEGIN{print "Output start!"};
{print};
END{print "Output done!"};
[root@ubuntu]awk_test:$ awk –f progfile awk_test.txt
为了方便后期维护,建议将AWK的程序文件以.awk作为后缀名。
另外,可以利用Shell的#!机制,将progfile内容改为:
#!/usr/bin/awk –f
BEGIN{print "Output start!"};
{print};
END{print "Output done!"};
这样的话,执行./progfile awk_test.txt即可。(通过type awk可以知道系统中awk命令的位置。)
注:使用#!机制的话,执行的shell命令./progfile实际上是执行:
“#!后面的命令” +”./progfile脚本” + “./progfile脚本的参数”
。另外,这种写法,awk后面最多只能跟一个参数;并且ARGV[0]的值在不同系统上可能表现不同,比如可能被解释为awk或/usr/bin/awk或./progfile。
比较特别的,如果要在program内使用或打印单引号,可以用其ASCII码’\47’表示。或者把程序写在文件中,这样就不用担心单引号和program外围的单引号混淆的问题。注意,在单引号中,反斜杠后接一个字符,会被解释成这个字符的字面意思,即和不加反斜杠是一样的含义。
你也可以通过下面两种方法来打印单引号:
awk 'BEGIN { print "Here is a single quote <'"'"'>" }'
或
awk 'BEGIN { print "Here is a single quote<'\''>" }'
AWK选项
-
-f program-file
执行文件中的程序,前面已讲过。
Program-file可以有多个,通常用来将通用的代码或函数做成库,以实现代码复用。
环境变量AWKPATH用来指定-f的搜索路径,如果不指定AWKPATH,则默认搜索“
.:/usr/local/share/awk
”,可以通过修改AWKPATH或ENVIRON[“AWKPATH”]来修改搜索路径,每个路径之间用冒号隔开(
.
或
::
都可以表示当前路径)。如果-f选项后面跟的是包含“/”的文件名,就不会去额外搜索路径了。
-
-v var=value
变量赋值,在BEGIN之前进行。
例如,定义变量name,并赋值为“jason”:
[root@ubuntu]awk_test:$ awk -v name=jason 'BEGIN{printf("name=%s\n", name)}'
name=jason
注意变量的引用不需要加“$”,实际上AWK的语法很多跟ANSI C的语法类似。“$”在AWK中是用来引用field的,后面会讲到。
-
-F fs
使用fs作为分隔符(默认是空格)
例如,打印/etc/passwd文件中的用户名一列:
[root@ubuntu]awk_test:$ cat /etc/passwd | awk -F ':' '{print $1}'
root
daemon
bin
sys
sync
games
-
–compat 或 –traditional
使用原生awk,不会识别gawk的扩展。例如,原生awk允许在while/for/do循环外面使用continue和break语句,这会被认为和next语句意思相同。Gawk添加–traditional则可以使用这一特性。 -
–dump-variables[=file]
输出排好序的AWK内置的全局变量到文件(若不指定文件,则默认为awkvars.out)
这个选项可以查看当前可用的内置全局变量。在编程的时候要注意,不要定义和这些内置变量重名的变量。
-
–non-decimal-data
识别输入流中的十六进制和八进制
例如,将下面文件中的数字相加求和:
[root@ubuntu]awk_test:$ cat number.txt
0x12
0x32
012
10
[root@ubuntu]awk_test:$ awk '{sum+=($1)}; END{print sum}' number.txt
22
[root@ubuntu]awk_test:$ awk --non-decimal-data '{sum+=($1)}; END{print sum}' number.txt
88
可以看到,如果不加–non-decimal-data选项,就只识别出十进制的12和10。
不过man gawk中说“Use this option with great caution!”,一方面这个选项可能破坏旧的程序,另一方面这个选项可能在以后被摒弃。
-
–profile[=prof_file]
生成awk命令的profile文件
这个选项以优雅的格式将awk命令保存到文件,如果不指定文件名,则默认为awkprof.out。
[root@ubuntu]awk_test:$ awk --non-decimal-data --profile '{sum+=($1)}; END{print sum}' number.txt
[root@ubuntu]awk_test:$ cat awkprof.out
# gawk profile, created Sun Jan 8 23:20:48 2017
# Rule(s)
{
sum += $1
}
# END block(s)
END {
print sum
}
如果使用pgawk执行命令,则还会显示每条语句以及每个函数的调用次数。
-
–re-interval
在正则表达式中支持间隔表达式
传统的awk不支持间隔表达式,必须加上–re-interval选项或者–posix选项才能使用。
-
-e program-text
或
–source program-text
program-text为awk的program源码,这个选项允许将文件中的源码和命令行中的源码混合使用。在需要引用自定义的库函数时就可以使用该选项,例如:
awk -f func_test.awk –source ‘BEGIN{printadd_INT(1,2)}’ awk_test.txt
这个例子中,func_test.awk文件里面定义了函数add_INT(),这里将-f指定的文件程序和–source指定的命令行程序混合在了一起。
-
-E file
或
–exec file
意义和-f选项相同,不过有两点区别:命令行中的其他选项都直接传给awk,而awk先处理其他选项和参数,最后才处理–exec选项;另外,不允许“var=value”形式的变量赋值。
这个选项应该在#!开头的脚本里使用,例如:
#! /usr/local/bin/gawk -E
awk program here …
这个选项可以防止向脚本里传递参数,因为所有的参数都先被awk识别并处理了。
-
–include source-file
-
–load ext
这两个选项都是针对引用函数库的,也可以在文件中使用@include和@load来引用库文件。不过在我所用系统的gawk不支持这两个参数以及相应的AWKLIBPATH环境变量,我就不介绍了。我们就使用-f来引用库文件吧,只是-f的文件里面可以是任何程序内容,并不是只针对库文件而设计的。
- — 标记选项的结束
这告诉awk,选项部分已经结束,可以用来传递以“-”开头的参数,而不被误认为是选项。
AWK的变量、Records和Fields
AWK的
变量
是动态生成的,在第一次被使用的时候开始存在。变量的值可以是下列数据类型:
浮点型
、
字符串类型
和
一维数组
。甚至一个变量既可以是浮点型也可以是字符串类型,这取决于代码中如何使用它。
AWK会将“var=value”形式的参数认为是变量赋值,例如下面这个命令,awk将“var=2”和“var=1”认为是给var赋值,而不是一个文件名,这个过程在awk顺序处理参数列表时进行的。
awk ‘var == 1 {print 1} var == 2 { print 2}’ var=2 awk_test2.txt var=1 awk_test1.txt
Records
一个record就是awk认为的一行输入,对一个输入流,默认以换行符分隔。不过可以通过内置变量
RS
来修改。例如,把RS赋值为Jan07:
[root@ubuntu]awk_test:$ awk -v RS=Jan07 '{print}' awk_test.txt
USER PID %MEM VSZ RSS STAT START TIME COMMAND
root 1 0.1 3652 1916 Ss
0:03 /sbin/init
root 2 0.0 0 0 S
0:00 [kthreadd]
root 3