每周一荐:文本处理工具AWK
上周给大家推荐了流编辑器sed,用sed其实已经可以干很多事情了。批量替换文件,批量查找指定的模式,支持单行和多行模式。但通过”sed单行脚本快速参考“可以看出,sed写出来的脚本可读性很差,有些甚至非常难以理解,特别是对于那些对sed的用法不是很熟悉的人来说,看起来简直像一堆毫无意义的字符乱码。
脚本写出来不是给自己看的,也要考虑到后续的维护。有没有更好的方式来做同样的事情,甚至比sed能干更多的事情了。awk登场了,它的语法类似与C,写起来非常方便并且非常容易理解,提供了许多系统变量、字符串处理函数、数值运算函数、美化输出函数,甚至可以自己定义函数。能想到的文本分析,AWK貌似都能搞定。下面列出一个关于AWK的快速参考:(悲剧,陪着测试做版本到早晨6点,天都亮了!)
命令行语法
调用awk的语法有两种基本形式:
awk [-v var=value] [-F re] [--] 'pattern {action}' var=value datafile(s) awk [-v var=value] [-F re] -f scriptfile [--] var=value datafile(s)
一个awk命令行是由命令,脚本和输入文件名组成的。输入是从命令中指定的文件中读取的。如果没有指定文件名为”-”,那么将从标准输入中读取。选项-F将字段分隔符(FS)设置为 re。
-v选项在脚本执行前将变量var的值设置为value。这个在BEGIN过程运行前执行。
根据POSXI参数分析约定,选项”–”标记命令行选项的结束。例如,利用这个选项你可以指定以”-”开头的datafile,否则这将和命令行选项混淆。
你可以在命令行指定一个由单引号包围的由pattern和action组成的脚本。换句话说,你也可以将脚本写入一个单独的文件并在命令行中用-f选项指定文件名scriptfile。
通过在脚本后命令行上指定参数,可以将它们传递到awk中,这包括设置系统变量,例如FO,OFS和RS。value可以是一个文字,一个shell变量($var)或一个命令的结果(`cmd`);如果其中包含空格或制表符则必须用引号包围起来。可以指定任意多个变量。
直到读取第一个输入行。命令行参数才能使用,因此在BEGIN过程中不能访问。参数按照它们出现在命令行中的顺序来求值,直到遇到一个文件名。出现在这个文件名后之后的参数在下一个文件名被识别时变为可用。
用shell实现调用awk
在系统提示符下输入脚本只能练习简单的单行脚本。任何可以作一个命令来调用并重用的脚本都可以放到shell脚本中。利用shell程序来调用awk可以使别人更容易使用这些脚本。
可以将调用awk的命令放在一个文件中,给它取要给名字以说明脚本的功能。将文件做成可以执行的(利用chmod命令)并将它放入到一个目录中,这个目录包含全局名。可以在命令行输入这个shell脚本的名字来执行awk脚本。这是为了更易于使用和重用。
在现代的unix系统中,包括linux,你可以用#!语法来创建自包含awk脚本:
#! /usr/bin/awk -f script
awk参数和输入文件名可以在调用shell脚本的命令行上指定。注意,使用的路径名取决于系统。
awk语言概要
这部分概括了awk如何处理输入记录和描述组成awk程序的各种语法要素。
记录和字段
每个输入行都被分隔为字段。默认情况下,字段定界符是一个或多个空格和/或制表符。可以使用-F命令行选项来改变字段分隔符。同时也设置了FS的值。下面命令行将字段分隔符改为一个冒汗:
awk -F: -f awkscr /etc/passwd
也可以将定界符复制给系统变量FS。这通常是在BEGIN过程中完成,但也可以作为命令行的参数来传递。
awk -f awkscr FS=: /etc/passwd
每个输入行都是几个字段组成的记录。每个字段可以根据它在这个记录中的位置来引用。”$1″表示第一个字段中的值,”$2″表示第二个字段的值等等。”$0″表示整个记录。下面的操作打印每个输入行的第一个字段:
{ print $1 }
默认的记录分隔符是一个换行符。下面的过程设置了FS和RS,使得awk将直到遇到空行前的任意个行解释为一个记录,而每个行是一个单独的字段。
BEGIN { FS="\n"; RS="" }
注意,当RS被设置为一个空字符时,不过FS的值是什么,换行符总是用了分隔字段。
脚本的格式
awk脚本包含一系列模式匹配规则和操作:
pattern {action}
action操作由一个或多个语句组成,用于对那些模式匹配的输入行执行操作。如果没有指定模式,这个操作。如果没有指定模式,这个操作对每个行都执行。下面的例子用print语句打印输入文件的每个行:
{ print }
如果指定一个模式,那么默认的操作由print语句构成,如上所示。
也可以出现函数的定义:
function name (parameter list) {statements}
这个语法定义了函数name,给出了在函数体中可以访问的参数列表。在参数列表中指定的变量被看做是函数内部的局部变量,其它所有的变量是全局的并可以在函数以为对它们进行访问。当调用自定义函数时,不允许在函数名和左圆括之间有空格个存在。在函数的定义中允许有空格。
行为终止
awk脚本中的行一个换行符或一个分好符终止。如果允许,可利用分号将多个行放在同一行,但这将降低程序的可读性。在语句之间允许存在空行。
程序控制语句(do,if,for或while)的范围包括下一行,在那一行列出了相关语句。如果给出了多个与控制语句相关的语句,那么必须用大括号括起来。
if (NF > 1) { name = $1 total += $2 }
对于使用反斜杠(\)转义符实现将一行代码写在多行上。也可以用下列字符中的任何一个来中断行。
, { && ||
在gawk中还是用”?”或”:”来延续一个行。字符串不能跨行(在gawk中除外,在gawk中可以在换行符的前面添加”\”来实现字符串换行)。
注释
注释以”#”开始并以换行符结束。它可以单独出现在一行上或出现在一行的最后。注释是描述性的说明,用于解释脚本有操作。注释不能用反斜杠来延伸到下一行。
模式
一个模式可以是下面所列的任何一个:
/regular expression/ relational express BEGIN END pattern,pattern
- 正则表达式使用元字符扩展集并且必须用斜杠包围。
- 关系表达式使用关系操作符。
- BEGIN模式在第一个输入行被读取之前应用,END模式在最后一个输入行被读取之后应用。
- 使用”!”可以否定匹配,也就是处理与模式不匹配的行。
- 可以访问若干行,和在sed中一样:pattern, pattern 除了BEGIN和END外,其它模式都可以使用下面的操作符来进行组合:&&(逻辑与), ||(逻辑或)。另外,C的条件操作符?:(pattern ? patttern:pattern)可以用于模式中。
- 可以将模式放在圆括号确保正确求值。
- BEGIN和END模式必须和操作想联系。如果编写了多个BEGIN和END规则,它们在被应用前被合成一个规则。
正则表达式
特殊字符 | 用法 |
---|---|
c | 和不是元字符的任何字符c匹配 |
\ | 转义它后面的任意元字符,包括它自己 |
^ | 将后面的正则表达定位在字符串的开始处 |
$ | 将前面的正则表达式定位在字符串的末端 |
. | 和任意单个字符匹配,包括换行符 |
[...] | 和用方括号包围起来的字符类中的任何一个字符匹配、脱字符(^)作为方括号中的第一个字符表示将匹配所有列在类中的字符以为的字符,连字符(-)用于表示一个字符范围。右方括号(])在作为类中第一个字符时将失去它们原来的含义。但\除外,它可以用转义],即使没有用在第一位。 |
r1 | r2 | 允许正则表达式r1或r2中的任何一个匹配 |
(r1)(r2) | 用于连接正则表达式 |
r* | 与前面的任意个(包括0个)正则表达式匹配 |
r+ | 与前面正则表达式的一个或多个出现匹配 |
r? | 与前面正则表达式的0个或1个出现匹配 |
(r) | 用于正则表达式的分组 |
符号 | 功能 |
---|---|
[.symbol.] | 比较符号。比较符号是一个多字符序列,应将它看做为一个单元来处理 |
[=equiv=] | 等价类。一个等价类列出了一组字符,这组字符应该被看做是相等的。 |
[:class:] | 字符类。字符类关键词表示不同的字符类。例如字母字符,控制字符等等 |
[:alnum:] | 字母数字字符 |
[:alpha:] | 字母字符 |
[:blank:] | 控制和制表符 |
[:cntrl:] | 控制字符 |
[:digit:] | 数字字符 |
[:graph:] | 可打印的和可见的字符 |
[:lower:] | 小写字符 |
[:print:] | 可打印的字符 |
[:punct:] | 标点符号字符 |
[:space:] | 空白字符 |
[:upper:] | 大写字符 |
[:xdigit:] | 十六进制数字 |
表达式
一个表达式可以由常量、变量、操作符和函数组成。 常量要么是字符串要么是数值。变量是一个符号,它引用一个值。可以将它看做是一条信息,返回一个特定数值或字符串的值。
常量
有两种类型的常量,即字符串常量或数值常量。字符串常量必须用引号括起来,而数值常量不需要。
转义序列
序列 | 描述 |
---|---|
\a | 报警字符 |
\b | 退格符 |
\f | 走纸符 |
\n | 换行符 |
\r | 回车符 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | 将字符表示为1到3位八进制值 |
\xhex | 将字符表示为十六进制 |
\c | 任何需要字面表示的字符c |
变量
有3种类型的变量:自定义变量,内置变量和字段。按照惯例,内置或系统变量的名字全部由大写字母组成。
变量的名字不能以数组开头,而是由字母,数字和下划线组成,在变量名中字母的大小写是很重要的。
变量不需要声明或初始化。一个变量可以包含一个字符串或数值。对于未被初始化的变量将空串(”")作为它的字符串,将0作为它的数值。awk会根据操作来决定一个值是作为字符串还是数值来处理。
变量的赋值形式为:
var=expr
它将表达式的值赋给var。下面的表达式将1赋给变量x。
x=1
使用变量的名字可以访问它的值:
{ print x }
以上语句打印变量x的值,本例可以得到1。
参加后面的“系统变量”一节了解内置变量的信息。利用n可以访问字段变量,这里n是0到NF之间的任意一个数,用于按位置访问字段。也可以表示为一个变量,例如NF表示最后一个字段,或表示一个常量,例如$1表示第一个字段。
数组
数组是一个可以用了存储一组值的变量。下面的语句为数组的元素赋一个值:
array[index] = value
在awk中,所有的数组都是关联数组。使得关联数组独特的是它的下标可以是一个字符串也可以是一个数字。
关联数组在数组的下标和元素之间建立一种“联系”。对于数组中的每个元素包含一对值:元素的下标和元素的值。关联数组的元素不像传统数组的元素那样按特定的顺序存储。
可以使用for循环来读取关联数组中的所有元素。
for (item in array)
这里数组的下标由变量item来指定,数组元素的值可以利用array[item]来测试这个元素的值。也可以使用delete语句从数组中删除一个元素。
系统变量
变量 | 描述 |
---|---|
ARGC | 命令行中的参数个数 |
ARGV | 包含命令行参数的数组 |
CONVFMT | 用于数字的字符串串转换格式(%.6g) |
ENVIRON | 环境变量的关联数组 |
FILENAME | 当前文件名 |
FNR | 和NR类似,但和当前文件相关 |
FS | 字段分隔符(一个空格) |
NF | 当前记录中的字段个数 |
NR | 当前记录的个数 |
OFMT | 数字的输出格式(%.6g) |
OFS | 输出字段分隔符(一个空格) |
ORS | 输出记录分隔符(一个换行符) |
RLENGTH | 和函数match()匹配的字符串的长度 |
RS | 记录分隔符(一个换行符) |
RSTART | 和函数match()匹配的字符串的第一个位置 |
SUBSEP | 数组下标的分隔字符(\034) |
操作符
操作符 | 描述 |
---|---|
= += -= *= /= %= ^= **= | 赋值操作符 |
?: | C语言的条件表达式 |
|| | 逻辑或 |
&& | 逻辑与 |
~ !~ | 匹配正则表达式与不匹配 |
< <= > >= != == | 关系操作符 |
(blank) | 连接符 |
+ - | 加法,减法 |
* / % | 乘法,除法,取模 |
+ – ! | 正,负,逻辑非 |
^ ** | 求幂 |
++ – | 递增和递减,作为前缀和后缀 |
$ | 字段引用 |
语句和函数
包含在大括号中的动作,由一个或多个语句和/或表达式组成。语句和函数之间的区别是函数将返回一个值,并且它的参数列表在圆括号中给出(在形式上不一定严格按照语法规定:printf被看做是一个语句,而它的参数列表可以包含在圆括号中;getline是一个函数,但它没有使用括号 )。
awk有许多预定义的算术函数和字符串函数。函数经常按下面的方式调用:
return = function(arg1, arg2)
这里的return是一个变量,用于保存函数的返回值(实际上,一个函数的返回值可以用在表达式的任何位置,而不仅仅只出现在赋值语句的右边)。函数的自变量是用逗号分隔的一个列表。左圆括号跟在函数名后面(对于内置函数,在函数名和括号之间允许有一个空格)。
awk的命令汇总
命令 | 说明 | 例子 |
---|---|---|
atan2() | atan2(y,x)返回y/x的反正切,单位是弧度。 | |
break | 从while, for, do循环中退出 | |
close() | close(filename-exppr)close(command-expr) 在大多数awk的是是实现中,你只能同时打开一定数量的文件和/或管道。因此,awk提供了一个close()函数,利用这个函数可以关闭一个文件或应该管道。它将打开文件或管道的同一表达式作为参数。这个表达式的每个字符必须和打开文件或管道所用的表达式相同——即使空格也是有意义的。 | |
continue | 开始while, for, do循环的下一个迭代 | |
cos() | cos(x)返回x的余弦,x单位为弧度 | |
delete | delete array[element]删除数组的元素 | |
do | do body while(expr) 循环语句。执行在body中的语句并计算expr的值,当expr的值为真时,重复执行body。 | |
exit | exit [expr]从脚本中退出,不再读取新行。如果有END规则,则执行。选项expr是awk的返回值。 | |
exp() | exp(x)返回e的x次幂(e^x) | |
for | for(init-expr; test-expr; incr-expr) statementC语言风格的循环结构。init-expr是计算器变量的初始值。test-expr是一个关系表达式并在每次执行statement之前计算它的值。当test-expr为假的时,退出循环。incr-expr用来在每次循环中递增计数器变量。
for(item in array) statement 用于读取关联数组的特殊循环。对于数组中的每个元素,statement都被执行;可利用array[item]的形式来访问元素。 |
|
getline | 读取下一输入行getline [var] [
第一种形式从file中读取输入,第二种形式读取command的输出结果。两种形式每次都只读取一行,并且每次执行该语句得到下一个输入行。输入行被赋给0并分解为字段,且设置NF,NR,FNR。如果指定了var,那么结构将赋给var而0不变。因此当结果被赋给一个变量时,当时行没有变化。实际上getline是一个函数,当它成功读取一个记录时返回值1,当遇到最后一行时返回0,当由于某些原因读取失败返回时返回-1。 |
|
gsub() | gsub(r, s, t)全局替换字符串s中与字符传t中的正则表达式r匹配的所有字符串。返回替换的次数,如果t没有给出,默认值为$0。 | |
if | if (expr) statement1 [else statement2] 条件语句。计算expr的值,如果为真,执行statement1;若果给出了else子句,当expr为假时执行statement2。 | |
index() | index(str, substr)返回字符串中的子串的位置(起始位置为1) | |
int() | int(x)通过将x中的小数点后面的数字截断得到x的整数值。 | |
length() | length(str)返回字符串的长度,如果没有参数则返回$0的长度。 | |
log() | log(x)返回x的自然对数(以e为底) | |
match | match(s, r)模式匹配函数,由正则表达式r给出模式,返回在字符串s中与r匹配的开始位置,如果没有发现匹配则返回0。将RSTART和ELENGTH的值分别设置为匹配的开始位置和匹配的长度。 | |
next | 读取下一个输入行并从第一个规则开始执行脚本 | |
print [output-expr] [dest-expr]求output-expr的值并将它直接输出到标准输出,后面跟着ORS的值。每个output-expr都被ORS的值分隔开。dest-expr是一个可选表达式,直接将输出送到一个文件或管道。”>file”直接将输出送到一个文件,并覆盖它的以前内容。”>>file”将输出追加到一个文件中,保留它以前的内容。在这种情况下,如果文件不存在都将创建这个文件。”|command”直接将输出作为一个系统命令的输入。 | ||
printf | printf (format-expr[, expr-list]) [dest-expr]从C语言中借用一个可选的输出语句。它可以产生格式化输出。它也可以用于输出没有自动换行的数据。format-expr是一个格式说明字符串和常量字符串(参加下一节关于格式说明符的列表)。expr-list是一个和格式说明符对应的参数列表。参加print语句关于dest-expr的描述。 | |
rand() | rand()生成0到1之间的一个随机数。每次执行脚本时这个函数返回相同一系列数据,除非使用srand()函数生成随机数发生器的种子数。 | |
return n | return [expr]使用用户定义的终端退出函数,返回表达式的值 | |
sin() | sin(x)返回x的正弦,x单位为弧度 | |
split() | split(str, array, sep)这个函数利用字段分隔符将字符串分解到数据元素中。如果没有指定字段分隔符,则使用FS的值。数组的分隔和字段的分隔是相同的。 | |
sprintf() | sprintf(format-expr[, expr-list])该函数返回根据printf格式说明指定的格式化的字符串。它格式化数据但不输出数据。format-expr是一个格式说明字符串和常量字符串。expr-list是一个和格式说明符对应的参数列表。 | |
sqrt() | sqrt(x)返回x的平方根 | |
srand() | srand(expr)使用expr为随机数发生器设置一个种子数。默认值为当天的时间。返回值为旧的种子数。 | |
sub | sub(r, s, t)替换字符串s中与字符串t中的正则表达式r匹配的第一个字符串。如果成功则返回1,否则返回0.如果没有给出t,默认值为$0。 | |
substr() | substr(str, beg, len)返回字符串str中开始位置为beg的子串,后面的字符串最大长为len。如果没有给出长度,将得到剩余的字符串。 | |
system() | system(command)该函数执行给出的command并返回它的状态。执行命令的状态通常表示成功或失败。0表示命令执行成功。非0表示某些类型的错误,不管是正的或负的,所执行的命令的有关文档将提供详细的介绍。在awk脚本中这个命令的输出结果是不可用的。使用”command | getline”可以将命令的输出读取到脚本中。 | |
tolower() | tolower(str)将字符串str中的所有大写字母转换为小写字母并返回新的字符串。 | |
toupper | toupper(str)将字符串str中的所有小写字母转换为大写字母并返回新的字符串。 | |
while | while(expr) statement循环结构。当expr为真时,执行statement。 |
应用在printf和sprintf中的格式表达式
格式表达式可以在%之后有3个可选的修饰符,格式说明符:
%-width.precision format-specifier
输出字段width是一个数值型的值。当你指定字段的宽度时,该字段的内容默认为右对齐。必须指定”-”来实现左对齐。因此,”%-20″输出的是一个字段宽度为20个字符的左对齐的字符串。如果这个字符串不足20个字符,则用空格来填满字段。
precision修饰符用于调整十进制浮点型的值,控制小数点左边出现的数字的个数。对于字符串格式,它控制从这个字符串中打印的字符的个数。
你可以根据printf或sprintf中的参数列表为width和precision动态赋值。要实现这一功能必须注上星号,而不是指定字面值。
printf("%*.*g\n", 5, 3, myvar)
在这个例子中,宽度为5,精度为3,将要打印的值来自于myvar。
注意,数值输出的默认精度是”%.6g”。这个默认值可以通过设置系统变量OFMT来改变。这将影响用print语句输出值的精度。
字符 | 描述 |
---|---|
C | ASCII字符 |
d | 十进制整数 |
i | 十进制整数,已经添加到POSIX中 |
e | 浮点型格式([-]d.precisione[+-]dd) |
E | 浮点型格式([-]d.precisionE[+-]dd) |
f | 浮点型格式([-]ddd.precision) |
g | e或f的轮回,无论哪一个最短,将末尾的0删除 |
G | E或f的轮回,无论哪一个最短,将末尾的0删除 |
o | 无符号的八进制值 |
s | 字符串 |
x | 无符号的十六进制,用a-f表示10-15 |
X | 无符号的十六进制,用A-F表示10-15 |
% | 字面% |
printf和sprintf的舍入方式通常取决于系统的C sprintf子例程。在许多机器上,sprintf舍入是“无偏差的”,这意味着它不总是对“.5”进行进位,因此1.5舍入得2。而4.5的舍入却为4。因此如果你利用一个格式来进行舍入计算,你应该知道你的系统是如何处理的。
参考资料
- 《sed and awk》
2012/04/27 05:56 于上海