GNU awk命令用法介绍

2/13/2017来源:经验技巧人气:2749

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{…}

例如下面的命令:

[[email protected]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的第二种格式来执行。
[[email protected]]awk_test:$ cat progfile 
BEGIN{print "Output start!"};
{print};
END{print "Output done!"};
[[email protected]]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”:

[[email protected]]awk_test:$ awk -v name=jason 'BEGIN{printf("name=%s\n", name)}'
name=jason

注意变量的引用不需要加“$”,实际上AWK的语法很多跟ANSI C的语法类似。“$”在AWK中是用来引用field的,后面会讲到。

 -F fs 使用fs作为分隔符(默认是空格)

例如,打印/etc/passwd文件中的用户名一列:
[[email protected]]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 识别输入流中的十六进制和八进制

例如,将下面文件中的数字相加求和:
[[email protected]]awk_test:$ cat number.txt 
0x12
0x32
012
10

[[email protected]]awk_test:$ awk '{sum+=($1)}; END{print sum}' number.txt 
22
[[email protected]]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。
[[email protected]]awk_test:$ awk --non-decimal-data --profile '{sum+=($1)}; END{print sum}' number.txt

[[email protected]]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:

[[email protected]]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  0.0      0     0 S    
   0:02 [ksoftirqd/0]
root        26  0.0      0     0 S    
   0:40 [kswapd0]
user       495  0.1   3588  1092 Ss   
   0:00 /sbin/udevd --daemon
user       860  0.0   3584   908 S    
   0:01 /sbin/udevd --daemon
user      1137  0.0   4520   776 S    
   0:00 smbd -F
user      1550  0.1   4521  1816 Ss   
   0:15 nmbd -D

看效果就知道是什么意思了。注意“Jan07”本身没有打印出来。

如果RS被赋值为单个字符,则这个字符就是records的分隔符;如果被赋值为多个字符,那RS实际上是一个正则表达式。

如果RS被赋值为空,则以空行作为record的分隔符。

fields

AWK会把一个record再分隔成一个个的field依次进行处理,以变量FS的值作为分隔符。

如果FS被赋值为单个字符,则这个字符就是fields的分隔符;如果被赋值为多个字符,那就是一个正则表达式;如果FS是空,则record中的每个字符都是分隔符。

注意,如果FS是空格,则多个空格、tab和换行,都会被认为是分隔符。

一个record中的各个field可以通过各自的位置来引用,依次为$1$2$3,...。而$0表示整个record。

如果给FIELDWIDTHS变量赋值为一个数值列表(以空格隔开),如下面的例子,record就会按照字符串长度来分隔fields而不是按照FS的值。这时FS变量会被忽略。

[[email protected]]awk_test:$ awk 'BEGIN{FIELDWIDTHS="2 4 4 4"}{print $1"||"$2"||"$3"||"$4}' awk_test.txt 
US||ER  ||    || PID
ro||ot  ||    ||   1
ro||ot  ||    ||   2
ro||ot  ||    ||   3
ro||ot  ||    ||  26
us||er  ||    || 495
us||er  ||    || 860
us||er  ||    ||1137
us||er  ||    ||1550

如果给FS重新赋值,就会切回到按照FS分隔fields。

NF变量用于获取当前record共分了多少了fields。你可以给NF、$0以及某个field赋值,那么相应record会更新并重新划分fields。例如:
[[email protected]]awk_test:$ awk '{NF-=1; if (FNR!=1) $1="json"; print}' awk_test.txt 
USER PID %MEM VSZ RSS STAT START TIME
json 1 0.1 3652 1916 Ss Jan07 0:03
json 2 0.0 0 0 S Jan07 0:00
json 3 0.0 0 0 S Jan07 0:02
json 26 0.0 0 0 S Jan07 0:40
json 495 0.1 3588 1092 Ss Jan07 0:00 /sbin/udevd
json 860 0.0 3584 908 S Jan07 0:01 /sbin/udevd
json 1137 0.0 4520 776 S Jan07 0:00 smbd
json 1550 0.1 4521 1816 Ss Jan07 0:15 nmbd
这个例子中,把NF减1,则原来的每个record的最后一个field就没有打印出来。FNR表示当前已经输入了几个record,例子中将文件除了第一行之外的第一个field的值都改成了“jason”。

内置变量

AWK有一些内置的全局变量可以直接使用,会使编程更方便。

内置变量

含义

ARGC

Awk命令行的参数个数(不包括选项和选项的值以及program部分),awk自身是第0个参数。

ARGV

Awk的参数列表,共ARGC个元素。

ARGIND

当前正在处理的文件处于ARGV数组中的index。

BINMODE

是否使用binmode打开文件。1("r"),2("w"),3("rw")分别表示输入文件、输出文件、所有文件需要使用binmode打开。

CONVFMT

数字的格式化类型,默认是"%.6g"。

ENVIRON

保存当前所有环境变量的数组。这个数组是以环境变量名作为下标的,例如ENVIRON["HOME"]的值是"/root"。

ERRNO

当getline或close失败,ERRNO会保存字符串形式的错误描述。

FIELDWIDTHS

一个以空格分隔的数值列表,设置这个值后,FS失效,record改以字符长度来划分fields。

FILENAME

Awk当前处理的文件的名字。注意,在BEGIN{ }中由于还没开始处理文件,FILENAME是空(除非被getline设置)。

FNR

表示正在处理的文件目前已经输入了几个record。

FS

Field分隔符,默认是空格。

IGNORECASE

是否忽略大小写,默认是0。用于控制正则表达式和字符串操作,例如会影响records和fields的分隔符。注意,数组下标不受影响。

LINT

如果为true,则对可能不兼容的awk命令提示warning,如果为fatal,则提示为错误。默认是0:不提示。

NF

当前record中的fields的数目。

NR

已经处理的record的数目(包括当前record)。

OFMT

输出中数字的格式,默认是“%.6g”。

OFS

输出中fields的分隔符,默认是空格。例如下面的命令,就会以” : ”来打印fields(注意$1 $2 $3之间要加逗号):

awk -v OFS=" : " '{print $1,$2,$3}' awk_test.txt

ORS

输出中records的分隔符,默认是新行。

PROCINFO

一个包含正在执行的awk命令自身信息的数组,以元素名作为下标,例如PROCINFO[“egid”],PROCINFO[“pid”]等,下面的命令可以列举所有的元素:

awk '{for (var in PROCINFO) print var;}' awk_test.txt

RS

records分隔符,默认是新行。

RT

record的结束符,通常被赋值为RS。

RSTART

match()匹配成功的子串的第一个字符在原始字符串中的index(从1开始)。0表示匹配失败。

RLENGTH

match()匹配成功的子串的长度。-1表示匹配失败(可以匹配空串,此时长度为0)。

SUBSEP

数组下标分隔符,默认为\034即0x1C的ASCII符号。在访问多维数组时有用。

TEXTDOMAIN

文本域,本地化的时候用到。

对于FS,强调一个‘FS =" "’和‘FS = "[ \t\n]+"’的不同,这两种FS都是将多个空格、tab或newline作为分隔符。但是前者会先将record中的前导空格和尾部空格剥掉,再决定如何分割fields。例如下面两条命令的结果就不同:

[[email protected]]awk_test:$ echo ' a b c d ' | awk '{ print $2 }'
b
[[email protected]]awk_test:$ echo ' a b c d ' | awk 'BEGIN { FS = "[ \t\n]+" }{ print $2 }'
A
也可以据此特征来删除前导空格:
[[email protected]]awk_test:$ echo '   a b c d' | awk '{ print; $2 = $2; print }'
   a b c d
a b c d

这条命令通过$2=$2,让awk重新划分fields,由于FS模式是空格,这时就会先把前导空格和尾部空格剥掉。

数组

AWK中的数组是关联数组(键值对的形式),数组下标放在方括号“[ ]”内,数组下标可以是数值或字符串。可以用(expr, expr ...)来模拟多维数组的下标。例如:

i = "A"; j = "B"; k = "C"
x[i, j, k] = "hello, world\n"

上面定义了数组x下标为"A\034B\034C"的值为"hello,world\n"。

操作符“in”可以用来测试下标是否存在,例如:

if (val in array)
print array[val]

如果是多维的下标,则写成 if ((i, j) in array) print array[i, j]

也可以用“in”来遍历数组:

下面两个例子,前者定义了一个一维数组,数组下标是[“a,b”]的元素。后者用来定义多维数组,例子中定义了三维数组中下标为[”a”][“,”][”b”]的元素。
[[email protected]]awk_test:$ awk 'BEGIN{array["a"",""b"]=1;for(i in array) print i}'
a,b
[[email protected]]awk_test:$ awk 'BEGIN{array["a",",","b"]=1;for(i in array) print i}'
a\034,\034b

记住,数组的小标总是字符串,如果以数值为下标,也会转换成字符串,也就是说,a[17]的下标是"17",并且和a[021]、a[0x11]是相同的下标。

使用delete可以删除一个数组元素,例如delete array[1],也可以delete array来删除整个数组。

变量类型转换

变量和fields可以是字符串或浮点数类型,一个变量和field的值是什么类型依赖于它处于的上下文,例如,如果用于数学表达式,就被当为数值类型。

如果需要强制变量或field被当作数值,则写成var+0。如果需要强制被当作字符串,则连接一个空串,例如var ""

虽然所有的数值都是浮点型的,但是对于一个整数,转换成字符串的结果就是整型,例如12会被转换为“12”而不是“12.00”。

在比较两个变量var1和var2时,如果两个变量都是浮点型,则按照数值来比较;如果一个浮点型,另一个是字符串,则按照数值来比较,这时,字符串变量被当做“数值字符串”,例如("12"==12)的值为1;如果两个变量都是字符串,则按照字符串来比较。

注意,只有在处理用户输入时,才会将形如“57”这种看起来像数值的字符串认为是“数值字符串”,例如getline的输入、FILENAME、ARGV的成员、ENVIRON的成员以及split()产生的数组元素。其他情况下,则只是一个字符串常量。

未初始化的变量,会有两个默认的初始值:数值0以及空字符串""。

八进制和十六进制

gawk识别八进制和十六进制,例如011、0x16。

字符串常量

字符串常量是指双引号内的字符序列。在字符串中,可以包含转义字符,如下:

转义字符

含义

\\

字面含义的反斜杠

\a

“alert”字符,通常是ASCII中的BEL

\b

backspace,回车

\f

form-feed,换页符

\n

newline,换行符

\r

carriage return,回车

\t

horizontal tab

\v

vertical tab

\xhex

十六进制数表示的ASCII码字符。例如\x2A表示ASCII中的“*”号

\ddd

一个、两个或三个数字组成的八进制数值表示的ASCII码字符。例如\052表示ASCII中的“*”号,注意\52同\052相同

\c

字面含义的字符c,例如需要使用原意的*号,则需要写成\*。

转义字符也可以用在正则表达式中,例如/[ \t\f\n\r\v]/表示匹配空白字符,/a\52b/同/a\*b/。

模式和动作

AWK是一个line-oriented的语言,对每一行(record),先进行模式匹配,再执行动作。动作的语句放在花括号{ }里面。如果模式为空,则对所有record执行动作,例如 { print } ,就是无条件的打印整个record。

用“#”对一行程序进行注释,直到换行则注释内容结束。

正常情况下,一个完整的语句以换行结束,但一个语句也可以写成多行,如果一个语句在“,”、“{”、“?”、“:”、“&&”“||”处换行,则认为该语句尚未完成;一个语句在一行中以do或else结尾,也会认为该语句未完成而继续读取下一行;其他情况下,可以在行末添加“\”来标记这一行尚未结束(建议不要在pattern或字符串以及注释的中间用反斜杠换行)。

也可以将多个语句写到一行中,语句之间用分号“;”隔开。

模式(patterns)

AWK的模式可以是下面的其中一种:

  BEGIN  END  /regular expression/  relational expression  pattern && pattern  pattern || pattern  pattern ? pattern : pattern  (pattern)  ! pattern  pattern1, pattern2

BEGINEND是两个比较特殊的模式,他们不对输入做模式匹配,因为BEGIN和END分别是在处理输入之前和之后。多个BEGIN块会被merge成一个BEGIN块,这个BEGIN里面的语句会在开始读取输入之前执行;多个END块会被merge成一个END块,这个END里面的语句会在处理完所有输入之后执行(或者执行exit)。注意, BEGIN和END必须是独立的,不能糅合在其他pattern表达式中。并且BEGIN和END不能没有action部分,也就是说BEGIN和END后面不能没有花括号{ }。

对于/regular expression/模式,会对所有匹配到该正则表达式的record执行其关联的语句。例如,打印包含“root”的行:

awk '/root/ {print $0}' awk_test.txt

一个relationalexpression可以使用后面将要讲到的任何运算符,这种模式通常用来测试一个field是否匹配某个正则表达式。关系表达式是指使用关系运算符(>,>=,<,<=,==,!=)连接一个或两个表达式组成的式子。

“&&”、“||”和“!”即我们熟知的与、或、非逻辑运算符,可以用来将多个patterns连接在一起,这时他们是short-circuite valuation(短路求值)的,例如对于&&操作,只要第一个值是false,那整个表达式就是false,就没必要计算后续的值了。圆括号( )可以改变运算符的运算顺序。

例如,打印第一个field不是“root”的行且第三个field等于“0.0”的行:
awk '$1 != "root" && $3 == "0.0" {print}' awk_test.txt

“?:”运算符和C语言中的三目的条件运算符一样,pattern1为true则选用pattern2,否则选用pattern3。

pattern1, pattern2形式的表达式被称为范围样式,它将匹配pattern1的record,匹配pattern2的record以及这两个record之间的所有record都匹配出来。

正则表达式

正则表达式由下表中一系列的字符集合组成。注:其中c表示character,r表示regular expression。

字符

含义

c

匹配非元字符c

\c

匹配元字符c的字面含义。由于元字符有特殊含义,因此必须加反斜杠来匹配其原始的字符含义。这些字符包括"["、"]"、"\"、"^"、"$"、"."、"|"、"?"、"*"、"+"、"{"、"}"、"("、")"等。

.

匹配一个字符(包括换行)

^

匹配字符串的开头

$

匹配字符串的结尾(我系统上不支持)

[abc...]

匹配字符列表abc…中的任何一个字符

[^abc...]

匹配字符列表abc…之外的任何一个字符

r1|r2

匹配r1或r2

r1r2

匹配r1且其后紧跟r2

r+

匹配一个或多个r

r*

匹配0个或多个r

r?

匹配0个或1个r

(r)

分组,匹配圆括号中的所有字符组合r

r{n}

r{n,}

r{n,m}

花括号内有1个或2个数值被称为区间表达式:只有一个数字n,则表示前面的正则表达式r重复n次;有一个数字n和一个逗号,表示r至少重复n次;有两个数字n和m,表示r重复n到m次。

区间表达式只有在指定--posix或--re-interval的时候才可用。

\y

匹配一个单词开头和结尾的空串,例如‘\yballs?\y’匹配‘ball’或‘balls’,但不匹配‘football’

\B

和\y相反

\s

匹配空白字符,同[[:space:]]

\S

匹配非空白字符,同[^[:space:]]

\<

匹配一个单词开头的空串,例如/\<away/匹配‘away’但不匹配‘stowaway’

\>

匹配一个单词结尾的空串

\w

匹配Word-constituent的字符(包括字母、数字和下划线),同‘[[:alnum:]_]’

\W

匹配非word-constituent的字符,同‘[^[:alnum:]_]’

\`

匹配一个buffer(字符串)开头的空串,同^

\'

匹配一个buffer结尾的空串,同$

上表中红色标记的字符用于连接多个正则表达式,我们称之为“正则表达式运算符”或“元字符”。

在字符串常量中有效的转义字符,在正则表达式中同样有效。

正则表达式通常用两个斜杠“/ /”包裹起来作为pattern使用,我们称之为正则表达式常量。

在中括号表达式中引用‘\’、‘] ’、‘-’或‘^’,需要加反斜杠,例如“[d\]]”匹配字符'd'或字符']'。

正则表达式的匹配遵循最左开始最长的匹配的原则,例如下面表达式的结果是"<A>bcd":

echo aaaabcd | awk '{ sub(/a+/, "<A>"); print }'

字符类是POSIX标准中引入的特性,一个字符类表示了某个特定属性的字符的集合。POSIX标准定义的字符类包括:

字符类

含义

[:alnum:]

字母或数字

[:alpha:]

字母

[:blank:]

空格或tab

[:cntrl:]

控制字符

[:digit:]

数字

[:graph:]

可打印且可见的字符(例如space可打印但不可见,而字符a既可打印又可见)

[:lower:]

小写字母

[:print:]

可打印的字符(非控制字符)

[:punct:]

标点符号(非字母、数字、控制字符和空白字符)

[:space:]

空白字符(例如空格、tab、换页符等等)

[:upper:]

大写字母

[:xdigit:]

十六进制数字

字符类在使用时必须放在正则表达式的方括号[ ]里面,例如要匹配包含字母’n’或者数字的record,可以这样写:

[[email protected]]awk_test:$ awk '/[n[:digit:]]/' regexp.txt

我们有时将匹配字母和数字写成/[A-Za-z0-9]/,但在本地化的时候,一些国家的字母表的字符集并不是这些字符,这时我们使用/[[:alnum:]]/就可以准确的匹配而不必担心本地化后的差异。

还有两个特殊的字符序列:Collating Symbols和Equivalence Classes,应用在非英语国家的本地化,例如用一个字符来表示多个字符/[[.ch.]]/或者定义多个等价字符/[[=e=]]/,有兴趣的可以自己查阅相关资料。

注意,\y,\B,\s,\S,\<,\>,\w,\W,\`和\'这些正则表达式的运算符是gawk特有的,gawk的不同选项可能影响如何解析正则表达式:

无选项:上述所有运算符均有效(区间表达式除外)。

--posix:只支持POSIX标准(包括区间表达式)的,那么\w就只表示字面意思的'w'。

--traditional:只支持传统的UNIX awk的正则表达式,此时,GNU的运算符、区间表达式、字符类都不支持,八进制和十六进制的转义字符("\ddd","\xhex")也会按字面意思解析。

--re-interval:支持区间表达式(即使有--traditional选项)。

我们可以通过将IGNORECASE设置为一个非0的值,使AWK在匹配时忽略大小写。也可以用toupper()/tolower()或中括号表达式,来只对某一些规则忽略大小写。

动作(actions)

AWK中的动作语句(action statements)都放在花括号{ }中,由大部分语言都有的赋值、条件和循环语句组成。AWK中的运算符、控制语句以及输入输出语句都是仿照C语言来定义的。

运算符

AWK中运算符按照优先级由高到低,列举如下:

运算符

含义

(…)

分组

$

field引用

++  --

自增、自减,均可前置和后置

^

幂运算(也可以用**,对应的幂运算赋值**=)

+  -  !

一元运算符的+、-、逻辑非

*  /  %

乘、除、取模

+  -

加、减

space

字符串连接

|  |&

管道IO、协同进程的管道IO。getline,print和printf使用

<  >  <=  >=  

!=  ==

关系运算符

~  !~

正则表达式的匹配、不匹配,一般用法是/exp/ ~ /regexp/,其中右侧可以是任何表达式(不需要必须是正则表达式常量)。例如$0 ~ /foo/意为查找匹配foo的$0,若匹配,则返回1,不匹配返回0。

in

获取数组成员

&&

逻辑与

||

逻辑或

?:

条件表达式,格式为expr1 ? expr2 : expr3,如果expr1为true,则表达式的值为expr2,否则为expr3

=  +=  -=  *= 

/=  %=  ^=

赋值运算符,均支持a=a+b和a+=b这两种形式。

控制语句

控制语句列举如下:

  if (condition) statement [ elsestatement ]  while (condition) statement  do statement while (condition)  for (initialization; condition; increment) statement  for (var in array) statement  switch (expression) {case value or regular expression:   case-bodydefault:   default-body}  break  continue  delete array[index]  delete array  exit [ expression ]  { statements }

这些控制语句和ANSI C中类似语句的语法相同。Switch的每个case分支通常要添加break语句以保证唯一匹配。

I/O语句

输入输出语句列举如下:

               I/O statement                                                      

解释

close(file [, how])

关闭文件、管道或协同进程。参数“how”只有在关闭协同进程的双向管道其中一端时才会用到,是字符串类型,可取"to"或"from"。

getline

把$0赋值为下一个输入的record。同时会更新NF、NR和FNR。

getline <file

把$0赋值为文件file的下一个record。同时会更新NF。

getline var

把变量var赋值为下一个输入的record。同时会更新NR和FNR。

getline var <file

把变量var赋值为文件file的下一个record。

command | getline [var]

执行command并将输出作为getline或getline var的输入。

command |& getline [var]

执行协同进程command并将输出作为getline或getline var的输入。(协同进程是gawk的扩展,command也可以是一个socket)

next

停止处理当前的record,并读取下一个record,然后重新从awk程序中第一个pattern开始处理新的record。如果当前已经达到输入数据中最后一个record,则开始执行END{ }程序块。

nextfile

停止处理当前的文件,并从下一个文件中读取record,然后重新从awk程序中第一个pattern开始处理新的record。FILENAME和ARGIND参数会被更新,FNR被重置为1。如果当前已经达到输入数据中最后一个record,则开始执行END{ }程序块。

print

打印当前的record(根据ORS的值判断record是否输出完毕)。

print expr-list

打印表达式列表。如果有多个表达式,每个表达式之间用OFS分隔。(根据ORS的值判断record是否输出完毕)

print expr-list >file

打印表达式列表到文件。如果有多个表达式,每个表达式之间用OFS分隔。(根据ORS的值判断record是否输出完毕)

printf fmt, expr-list

格式化的打印

printf fmt, expr-list >file

格式化的打印到文件。例如:

awk 'BEGIN{printf "%#x", 10 >"getnum.txt"}'

system(cmd-line)

执行命令cmd-line,并将exit status作为返回值。例如:

system("uname –a")

fflush([file])

清空已打开的输出文件或管道文件的所有缓存。如果不带file参数,则清空标准输出,如果file参数为null string,则所有打开的输出文件和管道的缓存都会被清空。

另外,print和printf也允许以下形式的输出重定向:

print ... >> file

将输出追加到文件file

print ... | command

向管道写内容

print ... |& command

向协同进程或socket发送数据

getline命令执行成功返回1,到达文件结尾返回0,出错返回-1。如果出错,则字符串ERRNO包含了出错信息。

注意,如果在打开一个双向(two-way)socket的时候失败,将会返回一个非致命的错误到调用者。

如果在一个循环中使用管道、协同进程或socket(向getline输出或从print/printf输入),你必须使用close()来创建新的command或socket的实例。AWK在管道、协同进程或socket返回EOF的时候不会自动关闭它们。

printf语句

AWK里的printf语句和sprintf()函数支持如下的标准格式转换符:

格式符

含义

%c

一个ASCII字符。如果传入的参数是一个数值,则将其转换为字符并打印;否则,认为传入的参数是字符串,只打印该字符串的第一个字符。

%d, %i

十进制数字(整数部分)

%e, %E

将一个浮点数以“[-]d.dddddde[+-]dd”的形式打印。%E使用大写的E。

%f, %F

将一个浮点数以“[-]ddd.dddddd”的形式打印。使用%F(需要系统库支持),则以大写字母显示"not a number"和"infinity"这类的值。

%g, %G

根据实际情况转换为%e或%f,选择其中最简短的格式。%G对应%E。

%o

无符号的八进制整数。

%u

无符号的十进制整数。

%s

字符串。

%x, %X

无符号的十六进制整数。

%%

字符'%'的原意(不会取实参)。

在这些格式符中,'%'和控制字符之间可以放置如下的额外参数(和C语言中printf的格式符用法相同):

count$

位置说明符,指定对第count个参数进行格式化转换。例如:printf("%2$d\n", 23, 24);打印的值是24。

width

指定格式化后的最短打印长度,如果不够长则填充空格,默认右对齐。

-

左对齐,通常与width搭配使用。

0

上述不足width的话,使用前导0填充而不是空格。只对数值类型有效。

+

对数值添加正负号。只对数值类型有效。

#

以另一种形式打印格式化的内容:例如%#o会在数值前面填0;%#x、%#X会在数值前面填0x或0X;对%#e、%#E、%#f和%#F,结果总会有小数点;%#g、%#G总会显示小数部分。

space

对正数,前面填一个空格;对负数,前面填负号。

.prec

对%e、%E、%f和%F,指明小数部分的最大位数;对%g、%G,指明有效数字的最大长度;对%d、%o、%i、%u、%x和%X,和面前的0width一样,指明最短打印长度,不足则前面补0;对%s,指明最长打印的字符数。

另外,printf和sprintf()支持动态的width和.prec:可以从参数列表中的值作为width或.prec的值。通过在count$前面加一个*号来达到这种效果。例如:

printf("%1$*2$s\n", "Bye bye!", 12);这个例子中,1$仍然是位置说明符的作用,而*2$的意思是将第二个参数替换在这个位置,这个语句就变成了:
printf("%1$12s\n", "Bye bye!", 12);

特殊文件

在通过print、printf、getline进行I/O重定向时,gawk可识别一些特定的文件名,并且支持从gawk的父进程(通常是shell)中继承这些文件的文件描述符来进行文件操作。同时,这些文件也可以用作命令行中的数据文件名。这些文件为:

/dev/stdin    标准输入

/dev/stdout  标准输出

/dev/stderr   标准错误输出

/dev/fd/n      与文件描述符n关联的文件

另外,使用破折号“-”也可以引用标准输入,例如:

some_command | awk -f myprog.awk file1 - file2

这条命令中,awk会先读取file1,然后读取some_command的输出,然后读取file2。

下面三个特殊的文件名,可以与协同进程操作符“|&”配合使用,创建TCP/ip网络连接。

/inet/tcp/lport/rhost/rport              本地端口是lport,与远程主机rhost、远程端口rport的TCP/IP连接,如果端口为0,则让系统自己选择端口。

/inet/udp/lport/rhost/rport      同上,只是TCP改为UDP。

/inet/raw/lport/rhost/rport             未使用,供后续扩展。

下面这些文件名用来获取当前正在运行的gawk进程的信息:/dev/pid,/dev/ppid,/dev/pgrpid和/dev/user。目前已被弃用,改为使用PROCINFO获取进程信息。

数值操作函数

AWK提供以下一些内置的算术函数:

函数

作用

atan2(y, x)

求y/x的反正切,单位是弧度。

cos(expr)

求余弦,expr的单位是弧度。

exp(expr)

求指数。

int(expr)

截断expr保留整数部分。

log(expr)

求自然对数。

rand()

生成一个随机数N,0 ≤ N < 1。

sin(expr)

求正弦,expr的单位是弧度。

sqrt(expr)

求平方根。

srand([expr])

为随机数生成器指定一个新种子expr。如果不指定expr,就使用时间作为种子。函数的返回值是之前的种子。

字符串函数

Gawk提供以下内置的字符串函数:

              函数                                

作用

asort(s [, d])

给数组s中的成员排序(按照gawk默认的升序排序方法),排序完成后,s的数组下标改为从1开始的整数序列。

如果指定第二个参数d,则排序的结果放在数组d中,原数组s不变化。

函数的返回值是原数组s的元素个数。

asorti(s [, d])

给数组s的下标排序,(按照gawk默认的升序排序方法),排序完成后,s的数组下标改为从1开始的整数序列,而原来的下标改为数组元素的值。原来的数组元素的值被丢弃。

如果不想丢弃原来的元素的值,可以指定第二个参数d,则排序的结果放在数组d中,原数组s不变化。

函数的返回值是原数组s的元素个数。

gensub(r, s, h [, t])

对原始字符串t,将匹配正则表达式r的子串替换为s。如果字符串h以’g’或’G’开头,则所有匹配都替换,否则只替换第一个匹配。函数的返回值即为执行替换后的字符串,也就是说,原始字符串t不会被修改。

如果不指定参数t,就从当前record中读取,即$0。

在s中,可以通过\1~\9来指代r中第n个圆括号中的匹配项,参考下面的例子1。\0或'&'则表示整个匹配的内容。

gsub(r, s [, t])

对原始字符串t,将匹配正则表达式r的子串全部替换为s。函数的返回值为匹配到的子串的个数,也就是说,原始字符串t会被修改。

如果不指定参数t,就从当前record中读取,即$0。

在s中,'&'则表示整个匹配的内容。如果要书写字符’&'的原意,要写作"\\&"。

index(s, t)

返回字符串t在字符串s中第一次出现的位置。例如index(“abcdefg”, “def”)返回4,即index从1开始。如果没找到子串t,则返回0。

length([s])

返回字符串s的长度,如果没有参数s,则返回$0的长度。也可以传入一个数组,这时返回数组元素的个数。

match(s, r [, a])

在字符串s中匹配正则表达式r,匹配成功则返回子串的位置(index从1开始),并更新RSTART和RLENGTH,匹配失败则返回0;

如果有第三个参数a,则正则表达式r中每个圆括号的匹配内容会被依次赋值给数组a[1]~a[n],而整个的匹配内容则赋值给a[0]。同时,a[m, "start"]和a[m, "length"]两个数组下标成员的值为a[m]在s中的位置和字符串长度。

见下面的例子2。

注意,如果在调用match()之前数组a不为空,则会先清空a。

split(s, a [, r])

将字符串s按照正则表达式r作为分隔符来分割,分割成的每个field保存在数组a中,函数返回fields的数量。

如果没有r参数,则根据FS来分割。

注意,如果在调用split()之前数组a不为空,则会先清空a。

sprintf(fmt, expr-list)

根据格式fmt打印表达式列表expr-list,最终的字符串作为返回值。例如:

sprintf("name%d:%s, name%d:%s", 1, "George", 2, "Tim");

strtonum(str)

将字符串转换为数值,可识别八进制(以0开头)和十六进制(以0x或0X开头)。例如strtonum(“34”),strtonum(34.50),strtonum(“017”)。

sub(r, s [, t])

同gsub,但只替换第一个匹配的子串。

substr(s, i [, n])

获取字符串s中,从第i个字符开始的n个字符形成的子串,该子串作为返回值。参数i从1开始。

tolower(str)

将str中的大写字母都转换为小写字母后作为函数返回值。

toupper(str)

将str中的小写字母都转换为大写字母后作为函数返回值。

注意,gawk3.1.5支持多字节的字符,这意味着index()/length()/substr()/match()都是针对字符起作用而不是字节。

例子1,匹配“me again”,然后将其中的me改为her:

[[email protected]]awk_test:$ awk 'BEGIN{result=gensub("(me) (again)", "her \\2", "g", "tell me again, you go again"); print result}' awk_test.txt 
tell her again, you go again

前面在sed命令中也讲到过这种用法,不过这里圆括号不需要加反斜杠转义,并且"\2"要写成"\\2"。

例子2,测试带第三个参数的match函数:
[[email protected]]awk_test:$ awk 'BEGIN{sstr="name1:George, name2:Tim";pos=match(sstr, "name[1-9]:([a-zA-Z]*), name[1-9]:([a-zA-Z]*)", list); \
print pos; print RSTART" "RLENGTH; for (i=0; i<=2; i++) print list[i], list[i, "start"], list[i, "length"];}' awk_test.txt
1
1 23
name1:George, name2:Tim, 1 23
George 7 6
Tim 21 3例子3,split函数测试:
[[email protected]]awk_test:$ awk 'BEGIN{sstr="name1:George, name2:Tim, name3:Jason";fcnt=split(sstr, list, "name[[:digit:]]+:"); print fcnt; \
for (i=0; i<=fcnt; i++) print list[i];}' awk_test.txt

时间函数

由于AWK主要的用处就是处理包含时间戳信息的大型日志文件。因此AWK提供了以下时间函数来获取和转换时间戳:

mktime(datespec)  将datespec转换为相对于1970-01-01零点的秒数。参数datespec的格式为:“YYYY MM DD HH MMSS[ DST]”,其中夏令时标志DST是可选的。例如mktime("2017 2 6 22 54 00")即为2017年2月6日22时54分0秒,注意这个时间是算上时区的。举例来说,在东8区,mktime("19701 1 8 0 1")的返回值为1。

strftime([format [, timestamp[, utc-flag]]])  将timestamp转换为format指定的格式,timestamp需为相对于1970-01-01零点的秒数。如果utc-flag不为0或null,则转换结果是UTC时间,否则是本地时间。如果不带timestamp参数,则使用当前时间;如果不带timestamp和format参数,则format默认与date命令结果的格式相同,format参数可用的格式请参考C语言中的strftime()函数,也可参考Effective AWKProgramming[2]中的说明。

systime()  将当前时间转换为相对于1970-01-01零点的秒数。

位操作函数

Gawk3.1开始的版本,提供了下面的位运算函数:

函数

作用

and(v1, v2)

按位与

compl(val)

按位取反,同C语言中的'~'运算符

lshift(val, count)     

val左移count位,即val << count

or(v1, v2)

按位或

rshift(val, count)

val右移count位(高位补符号位),即val >> count

xor(v1, v2)

按位异或

自定义函数

AWK中可以自定义函数,形式如下:

function name(parameter list) { statements }

func name(parameter list) { statements }

其中,name是函数名,前面添加关键字function或func。圆括号内是形参列表,后面大括号内书写函数的实现。

例如下面的例子:

function add_INT(a, b)
{
    return (a+b);
}

function add_ARRAY(array)
{
    sum = 0;
    for(i in array)
    {
        sum += array[i];
    }
    return sum;
}

BEGIN {
aint[0]=10; aint[1]=11; aint[2]=12;
print add_ARRAY(aint); print add_INT(12, 78);
}

注意,对于自定义函数,在调用函数时,函数名和左圆括号之间不能有空格(AWK的内置函数没有这个限制)。

在AWK中,所有的变量都是全局的,那么就可能出现一个函数中的局部变量和主程序的变量重名。如果想避免这种情况,可以在函数的参数列表里指明局部变量,方法是将局部变量写在形参后面,并且用多个空格与形参列表分开。

例如:
function test(a, b,     c)
{
    c = 20;
    sum = a + b;
}

BEGIN {
c = 10;
sum = 5;
test(11, 22);
print c" "sum;
}

这个例子中,有c和sum两个变量,在函数test中,会修改c和sum,但是由于指明了c在test()中是作为局部变量的,因此不会影响主程序中c的值。所以print打印出的结果是“10 33”。

实际上,参数列表里的形参都会被认为是局部的,用多个空格分开的做法只是为了代码的可读性。

当需要将一段通用代码制作成库函数时,自定义函数就派上了用场,可以配合--source选项来在命令行程序中引用库函数。

一些建议:在自定义函数内部,尽量避免定义外部可能使用的变量,例如“i”,“j”这样的变量,在外部程序中很可能用到,因此在函数内部就不要用这样的变量命名。建议在函数内变量命名时以“_”开头来避免冲突。另外,变量和函数的命名尽量体现它的作用和含义。最后,如果函数内定义了外部可以使用的全局变量,变量名可以第一个字母大写,如“Optind”以和局部变量区别(不全部大写是为了防止和AWK内置变量混淆)。

可以参考Effective AWK Programming[2]中第10章的部分库函数的实现和第11章的自定义函数举例来学习自定义函数的写法。

信号

pgawk可接收SIGUSR1和SIGHUP两个信号。SIGUSR1信号会使pgawk生成profile文件(如之前--profile所述),包含自定义函数的调用栈,之后pgawk程序继续运行。SIGHUP信号同样会产生profile文件,之后pgawk程序退出。

返回值

Gawk执行正确返回EXIT_SUCCESS,通常是0;执行失败返回EXIT_FAILURE,通常是1;如果发生严重错误,返回2,但有些系统上会返回EXIT_FAILURE。

如果exit语句指定了返回值,则gawk返回这个指定的值。

参考资料

[1] Gawk(1) manpage for GNU Awk 3.1.8

[2] Effective AWK Programming: http://www.gnu.org/software/gawk/manual/gawk.html