分类: Python/Ruby
2010-12-20 12:00:35
condition {action}
如$ awk '/root/ print' 或$ awk '$3 < 100 print $3'。
where condition is typically an expression and action is a
series of commands. The input is split into records, where by default
records are separated by newline characters so that the input is split
into lines. 默认情况下,每一个输入行都是一条记录,但用户可通过RS变量指定不同的分隔符进行分隔。The program tests each record against each of the conditions
in turn, and executes the action for each expression that is true.
condition和action都是可缺省的。The condition defaults to matching every record. The default action is to print the record.如果没有指定条件,则action应用到全部记录,如果没有action,则输出匹配的全部记录。
(一)awk程序执行方式
1) 命令行程序:awk [options] 'prog' [var=value] [file(s)]
如果没有指定file(s),则表示从终端读取输入。
2) AWK scripts程序:awk [options] -f scriptfile [var=value] [file(s)]
As with many other programming languages, self-contained AWK script can be constructed using the so-called "shebang" syntax.
For example, a UNIX command called hello.awk that prints the string "Hello, world!" may be built by creating a file named hello.awk containing the following lines:
#!/usr/bin/awk -f
BEGIN { print "Hello, world!" }
条件表达式可以是以下几种:
1.正则表达式:/pattern/
2.关系表达式:可以使用运算符表中的关系运算符进行操作。
3.模式匹配表达式:使用用运算符~(匹配)和!~(不匹配)。
4.模式范围表达式(模式A,模式B):指定一个行的范围。该语法不能包括BEGIN和END模式。
5.BEGIN和END:前者指定在第一条输入记录被处理之前所发生的动作,可以用于在没有任何输入的情况下进行测试,它通常用来改变内建变量的值,如OFS,RS和FS等,以及打印标题、设置全局变量等;后者指定在最后一条输入记录被读取之后发生的动作,执行块中的所有动作在整个输入文件处理完成后被执行。
awk运算符
运算符 描述
= += -= *= /= %= ^= **= 赋值运算符
?: C条件表达式
|| 逻辑或
&& 逻辑与
~ !~ 匹配正则表达式和不匹配正则表达式
< <= > >= != == 关系运算符
空格 连接
+ - 加,减
* / & 乘,除与求余
+ - ! 一元加,减和逻辑非
^ ** 求幂
++ -- 增加或减少,作为前缀或后缀
$ 变量引用
in 数组成员
(三)awk动作表达式
awk动作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内。主要有以下几类:
1.变量或数组赋值
2.输出命令
3.函数表达式
4.控制流命令
a)在awk中,变量不需要预先定义就可以直接使用,变量类型可以是数字或字符串。awk的内置变量有:
变量 描述
$n 域变量。表示当前记录的第n个字段,字段间由FS分隔。
$0 表示整个当前记录。
ARGC 命令行参数的数目。
ARGIND 命令行中当前文件的位置(从0开始)。
ARGV 包含命令行参数的数组。
CONVFMT 数字转换格式(默认值为%.6g)
ENVIRON 环境变量关联数组。
ERRNO 最后一个系统错误的描述。
FIELDWIDTHS 字段宽度列表(用空格键分隔)。
FILENAME 当前文件名。
FNR 同NR,但相对于当前文件。
FS 字段分隔符(默认是任何空格)。
IGNORECASE 如果为真,则进行忽略大小写的匹配。
NF 当前记录中的字段数。
NR 当前记录数。
OFMT 数字的输出格式(默认值是%.6g)。
OFS 输出字段分隔符(默认值是一个空格)。
ORS 输出记录分隔符(默认值是一个换行符)。
RLENGTH 由match函数所匹配的字符串的长度。
RS 记录分隔符(默认是一个换行符)。
RSTART 由match函数所匹配的字符串的第一个位置。
SUBSEP 数组下标分隔符(默认值是\034)。
变量赋值格式:Variable = expression
awk可以在命令行中给变量赋值,然后将这个变量传输给awk脚本。如$ awk -F: -f awkscript VarA=4 VarB=2004 srcfile,上式中的VarA和VarB都是自定义变量,分别被赋值为4和2004。在awk脚本中,这些变量使用起来就象是在脚本中建立的一样。注意,如果参数前面出现srcfile,那么在BEGIN语句中的变量就不能被使用。
域变量也可被赋值和修改,如$ awk '{$2 = 100 + $1; print }' test,上式表示,如果第二个域不存在,awk将计算表达式100加$1的值,并将其赋值给$2,如果第二个域存在,则用表达式的值覆盖$2原来的值。再例如:$ awk '$1 == "root"{$1 ="test";print}' srcfile,如果第一个域的值是“root”,则把它赋值为“test”,注意,如果其中没有使用双引号表示使用字符串,则会被当作普通变量。
数组的表示:awk中的数组下标可以是数字和字母,称为关联数组。
用变量作为数组下标:如 $ awk '{name[x++]=$2};END{for(i=0;i
用字符串作为数组下标:如 count["test"]
用关键字in指定数组中的元素(常用于for循环中读取关联数组中的每一个元素):
例如:{
for (item in arrayname){
print arrayname[item]
}
}
用域值作为数组的下标:如 $ awk '{count[$1]++} END{for(name in count) print name,count[name]}' srcfile。该语句将打印$1中各个不同的字符串出现的次数。它首先以第一个记录中的第一个域作为数组count的下标,以后的每个记录中,若第一个域变化,索引就变化。
delete函数用于删除数组元素。如:$ awk '{line[x++]=$1} END{for(x in line) delete(line[x])}' srcfile。分配给数组line的是第一个域的值,所有记录处理完成后,for循环将删除每一个数组元素。
The print command is used to output text. The output text is always terminated with a predefined string called the output record separator (ORS) whose default value is a newline. The simplest form of this command is:
print
This displays the contents of the current record. In AWK, records are broken down into fields, and these can be displayed separately:
print $1
Displays the first field of the current record
print $1, $3
Displays the first and third fields of the current record, separated by a predefined string called the output field separator (OFS) whose default value is a single space character
Although these fields ($X) may bear resemblance to variables (the $ symbol indicates variables in perl), they actually refer to the fields of the current record. A special case, $0, refers to the entire record. In fact, the commands "print" and "print $0" are identical in functionality.
The print command can also display the results of calculations and/or function calls:
print 3+2
print foobar(3)
print foobar(variable)
print sin(3-2)
c)函数表达式:awk中的函数定义与使用与C语言类似。
awk的内建函数有:
字符串函数:
sub(r, s, t) 将 t 中匹配 r 的最左边最长的子串替换成 s,返回是否发生了替换。
sub(r,s) 将$0 中匹配 r 的最左边最长的子串替换成 s
gsub(r,s,t) 将 t 中每一处匹配 r 的子串替代成 s,返回是否发生了替换。
gsub(r,s) 将$0 中每一处匹配 r 的子串替代成 s
substr(s,p) 返回字符串 s 中从位置 p 开始的后缀部分。
substr(s,p,n) 返回字符串 s 中从位置 p 开始,长度为 n 的子串。
index(s,t) 返回 s 中字符串 t 的首位置。
length(s) 返回 s 的长度,在未指定变元 s 时返回 $0 的长度。
match(s,r) 返回 s 中匹配 r 的最左边子串的起始位置,并(按最长匹配方式)设置预定义变量 RSTART 和 RLENGTH 的值。
split(s,a,fs) 根据 fs 中的分隔符将 s 分成序列,将每一个片段赋值给下标从 1 开始的数组 a,并返回分割的字符串元素的数量。
printf(fmt,exp) 将 exp 按照 fmt 格式化后输出。
sprintf(fmt,exp) 返回经fmt格式化后的exp。
时间函数:
systime() 返回从1970年1月1日开始到当前时间(不计闰年)的整秒数。
systime( [format specification][,timestamp] ) 使用C函数库中的strftime函数格式化时间。
systime( [format specification][,timestamp] )
实例:
$ awk '{ now = systime(); print now }'
$ awk '{ now=strftime( "%D", systime() ); print now }'
$ awk '{ now=strftime("%m/%d/%y"); print now }'
日期和时间格式说明符
格式 描述
%a 星期几的缩写(Sun)
%A 星期几的完整写法(Sunday)
%b 月名的缩写(Oct)
%B 月名的完整写法(October)
%c 本地日期和时间
%d 十进制日期
%D 日期 08/20/99
%e 日期,如果只有一位会补上一个空格
%H 用十进制表示24小时格式的小时
%I 用十进制表示12小时格式的小时
%j 从1月1日起一年中的第几天
%m 十进制表示的月份
%M 十进制表示的分钟
%p 12小时表示法(AM/PM)
%S 十进制表示的秒
%U 十进制表示的一年中的第几个星期(星期天作为一个星期的开始)
%w 十进制表示的星期几(星期天是0)
%W 十进制表示的一年中的第几个星期(星期一作为一个星期的开始)
%x 重新设置本地日期(08/20/99)
%X 重新设置本地时间(12:00:00)
%y 两位数字表示的年(99)
%Y 当前月份
%Z 时区(PDT)
%% 百分号(%)
数学函数:
函数名称 返回值
atan2(x,y) y,x范围内的余切
cos(x) 余弦函数
exp(x) 求幂
int(x) 取整
log(x) 自然对数
rand() 随机数
sin(x) 正弦
sqrt(x) 平方根
srand(x) x是rand()函数的种子
int(x) 取整,过程没有舍入
rand() 产生一个大于等于0而小于1的随机数
自定义函数:
在awk中可自定义函数,格式如下:
function name ( parameter, parameter, parameter, ... ) {
statements
return expression # the return statement and expression are optional
}
d)控制流命令:awk中的控制流语句是从C语言中借鉴过来的,用于控制程序的流程。
条件判断语句:
if语句格式:{
if (expression){
statement; statement; ...
}
}
if、else和else if语句格式,可用于多重判断:
格式:{
if (expression){
statement; statement; ...
}
else if (expression){
statement; statement; ...
}
else {
statement; statement; ...
}
}
循环控制:awk使用while和for来控制循环执行,例如:
$ awk '{ i = 1; while ( i <= NF ) { print NF,$i; i++}}' srcfile。变量的初始值为1,若i小于或等于NF(记录中域的个数),则执行打印语句,且i增加1。直到i的值大于NF.
$ awk '{for (i = 1; i
for的循环条件可使用数组,如:{
for (item in arrayname){
print arrayname[item]
}
}
breadk、continue语句。break用于在满足条件的情况下跳出循环;continue用于在满足条件的情况下忽略后面的语句,直接返回循环的顶端。如:
{for ( x=3; x<=NF; x++)
if ($x==0){print "Get next record"; continue}
if ($x==9){print "Found"; break}
}
next语句用于从输入文件中读取一行,然后从头开始执行awk脚本。如:
{ if ($1 ~/test/){next}
else {print} }
exit语句用于结束awk程序,但不会略过END块。退出状态为0代表成功,非零值表示出错。
(四)awk与shell环境
在GNU版本的awk程序中,可使用shell的重定向符进行重定向输出,也可以使用getline函数从标准输入、管道或者当前正在处理的文件之外的其他输入文件获得输入。
$ awk 'BEGIN{ "date" | getline d; print d}' test。执行linux的date命令,并通过管道输出给getline,然后再把输出赋值给自定义变量d,并打印它。
$ awk 'BEGIN{printf "What is your name?"; getline name < "/dev/tty" } $1 ~name {print "Found" name on line ", NR "."} END{print "See you," name "."} test。在屏幕上打印”What is your name?",并等待用户应答。当一行输入完毕后,getline函数从终端接收该行输入,并把它储存在自定义变量name中。如果第一个域匹配变量 name的值,print函数就被执行,END块打印See you和name的值。
此外,GNU版本的awk程序还可以使用system函数执行系统命令,以及使用fflush函数刷新输出缓冲区。
简介: Awk 是一种名称奇怪但功能强大的语言。本文是一个包含三部分的系列的第一篇。在本文中,DanielRobbins 将使您迅速掌握 awk 编程技巧。随着本系列的进展,将讨论更高级的主题,最后将演示一个真实的高级 awk 应用程序。
在这一系列的文章中,我将使您成为精通 awk 的编程人员。我承认,awk 并没有一个非常好听且又非常 “时髦” 的名字。awk 的 GNU 版本(叫作 gawk)听起来非常怪异。那些不熟悉这种语言的人可能听说过 "awk",而且可能认为它是一组落伍且过时的混乱代码。它甚至会使最博学的 UNIX 权威陷于混乱的边缘(使他不断地发出 "kill -9!" 命令,就象使用咖啡机一样)。
的确,awk 没有一个动听的名字。但它是一种很棒的语言。awk 适合于文本处理和报表生成,它还有许多精心设计的特性,允许进行多种方式的编程。与某些语言不同,awk 的语法较为常见。它借鉴了某些语言的一些精华部分,如 C 语言、python 和 bash(虽然在技术上,awk 比 python 和 bash 早创建)。awk 是一种一旦学会就会成为您战略代码库的重要部分的语言。
为了理解awk是如何工作的,在命令行中输入如下命令:
$ awk '{ print }' /etc/passwd您应该会看到 /etc/passwd 文件中的内容,本文使用该文件来解释 awk 的工作原理。当调用 awk 时,我们指定 /etc/passwd 作为输入文件。Awk 在执行期间对 /etc/passwd 文件中的每一行依次执行 print 命令。所有输出都发送到 stdout,可以得到类似 cat 命令的结果。
现在解释代码块 { print }。在 Awk 中,花括号用于将代码分块,这与 C 语言类似。我们的代码块中只有一条 print 命令。在 Awk 中,当 print 命令单独出现时,将打印当前行的全部内容。
$ awk '{ print $0 }' /etc/passwdIn awk, the $0 variable represents the entire current line, so print and print $0 do exactly the same thing. 在 Awk 中,变量 $0 表示整个当前行,因此 print 和 print $0 的作用完全相同。 If you'd like, you can create an awk program that will output data totally unrelated to the input data. Here's an example:
$ awk '{ print "" }' /etc/passwd
Whenever you pass the "" string to the print command, it prints a blank line. If you test this script, you'll find that awk outputs one blank line for every line in your /etc/passwd file. Again, this is because awk executes your script for every line in the input file. Here's another example:
$ awk '{ print "hiya" }' /etc/passwd 运行该脚本,屏幕上讲显示多行 hiya。:)print $1
输出:As you can see, awk prints out the first and third fields of the /etc/passwd file, which happen to be the username and uid fields respectively. Now, while the script did work, it's not perfect -- there aren't any spaces between the two output fields! If you're used to programming in bash or python, you may have expected the print $1 $3 command to insert a space between the two fields. However, when two strings appear next to each other in an awk program, awk concatenates them without adding an intermediate space. The following command will insert a space between both fields:
$ awk -F":" '{ print $1 " " $3 }' /etc/passwdWhen you call print this way, it'll concatenate $1, " ", and $3, creating readable output. Of course, we can also insert some text labels if needed:
$ awk -F":" '{ print "username: " $1 "\t\tuid:" $3" }' /etc/passwdPutting your scripts in their own text files also allows you to take advantage of additional awk features. For example, this multi-line script does the same thing as one of our earlier one-liners, printing out the first field of each line in /etc/passwd:
BEGIN { FS=":" } { print $1 }与前面使用命令行脚本的方法相比,两者的区别在于如何设置域段分隔符。在该脚本中,(通过设置 FS 变量)在代码中指定字段分隔符,而前一示例通过在命令行向 awk 传递 -F":" 选项来设置 FS。一般而言,最好在脚本内部设置字段分隔符,因为这样可以少输入一个命令行参数。本文稍后将深入讲解 FS 变量。
通常,awk 会针对每个输入行执行一次每个代码块。但是,在许多编程情形下,可能需要在 awk 开始处理输入文件的文本之前 执行初始化代码。对这种情况,awk 支持定义 BEGIN 代码块。前一示例使用了这种代码块。因为 BEGIN 代码块在 awk 开始处理输入文件之前执行,因此它是初始化 FS(字段分隔符)变量、打印页眉或者初始化在后续程序中将要引用的其他全局变量的绝佳位置。
另外,awk 还提供了另一种称为 END 的专用代码块。在输入文件的所有行处理完毕之后,awk 执行这个代码块。通常,END 代码块用于进行最终计算或者打印应该在输出流结尾处出现的汇总信息。
There are many other ways to selectively execute a block of code. We can place any kind of boolean expression before a code block to control when a particular block is executed. Awk will execute a code block only if the preceding boolean expression evaluates to true. The following example script will output the third field of all lines that have a first field equal to fred. If the first field of the current line is not equal to fred, awk will continue processing the file and will not execute the print statement for the current line:
$1 == "fred" { print $3 }Awk also offers very nice C-like if statements. If you'd like, you could
rewrite the previous script using an if
statement.
如,下面的条件匹配表达式:
$5 ~ /root/ { print $3 }
可以改写成 C 语言风格的 if 条件表达式:
Here's a more complicated example of an awk if statement. As you can see, even with complex, nested conditionals, if statements look identical to their C counterparts:
{Using if statements, we can also transform this code (对下面的否定条件匹配表达式):
两个脚本都会只输出不 包含 matchme 字符序列的行。也可以选择最适合您的代码的方法。它们的功能完全相同。
Awk also allows the use of boolean operators "||" (for "logical or") and
"&&"(for "logical and") to allow the creation of more
complex boolean expressions:
该示例只打印第一个字段等于 foo 且 第二字段等于 bar 的行。
So far, we've either printed strings, the entire line, or specific fields. However, awk also allows us to perform both integer and floating point math. Using mathematical expressions, it's very easy to write a script that counts the number of blank lines in a file. Here's one that does just that:
BEGIN { x=0 }在 BEGIN 代码块中,我们将整型变量 x 初始化为零。这样,awk 每次遇到空白行时都将执行 x=x+1 语句,递增 x 值。在所有行都处理完毕之后,awk 执行 END 代码块,并打印最终的汇总信息,以显示它找到的空白行数。
稍做试验就可以发现,如果特定变量不包含有效数字,那么 awk 在计算数学表达式时将该变量当作数值零处理。
Awk 的另一个优点是它拥有全面的数学运算符。除了标准的加减乘除,awk 还支持前面演示的指数运算符 “^”、求模(余数)运算符 “%” 和借鉴自 C 语言的大量方便的赋值运算符。
其中包括前后加/减(i++、--foo), 加减乘除赋值运算符(a+=3、b*=2、c/=2.2、d-=6.2)。而且,这仅仅是一部分 —— 我们还能使用方便的求模/指数赋值运算符(a^=2、b%=4)。
Awk 有其自己的特殊变量集合。其中一些变量支持调优 awk 性能,而且可以读取另一些变量来收集关于输入的重要信息。前面已经接触过特殊变量 FS。如前所述,这个变量支持设置 awk 期望在字段中找到的字符序列。当我们使用 /ect/passwd 作为输入时,FS 设为 ":"。尽管这样做可以解决问题,但 FS 还支持更高的灵活性。
FS="\t+"上面使用特殊的正则表达式字符 “+”,表示 “一个或多个前一字符”。
FS="[[:space:]+]"尽管该赋值运算符能够解决问题,但是并非必要。为什么呢?因为在默认情况下,FS 被设为单个空格字符,awk 将其解释为 “一个或多个空格或制表符”。在这个特定的示例中,默认的 FS 设置恰好是您最想要的设置。
FS="foo[0-9][0-9][0-9]"()
Awk 还提供了一些具有多种用途的其他变量。后续文章中将深入讲解这些变量。
awk 是一种用于读取和处理结构化数据(如系统的 /etc/passwd 文件)的极佳工具。/etc/passwd 是 UNIX 用户数据库,并且是用冒号定界的文本文件,它包含许多重要信息,包括所有现有用户帐户和用户标识,以及其它信息。在我的 前一篇文章 中,我演示了 awk 如何轻松地分析这个文件。我们只须将 FS(字段分隔符)变量设置成 ":"。
正确设置了 FS 变量之后,就可以将 awk 配置成分析几乎任何类型的结构化数据,只要这些数据是每行一个记录。然而,如果要分析占据多行的记录,仅仅依靠设置 FS 是不够的。在这些情况下,我们还需要修改 RS 记录分隔符变量。RS 变量告诉 awk 当前记录什么时候结束,新记录什么时候开始。
譬如,让我们讨论一下如何完成处理“联邦证人保护计划”所涉及人员的地址列表的任务:
理论上,我们希望 awk 将每 3 行看作是一个独立的记录,而不是三个独立的记录。如果 awk 将地址的第一行看作是第一个字段 ($1),街道地址看作是第二个字段 ($2),城市、州和邮政编码看作是第三个字段 $3,那么这个代码就会变得很简单。以下就是我们想要得到的代码:
BEGIN {在 address.awk 的 print 语句中,可以看到 awk 会连接(合并)一行中彼此相邻的字符串。我们使用此功能在同一行上的三个字段之间插入一个逗号和空格 (", ")。这个方法虽然有用,但比较难看。与其在字段间插入 ", " 字符串,倒不如让通过设置一个特殊 awk 变量 OFS,让 awk 完成这件事。请参考下面这个代码片断。
print "Hello", "there", "Jim!"这行代码中的逗号并不是实际文字字符串的一部分。事实上,它们告诉 awk "Hello"、"there" 和 "Jim!" 是单独的字段,并且应该在每个字符串之间打印 OFS 变量。缺省情况下,awk 产生以下输出:
Hello there Jim!这是缺省情况下的输出结果,OFS 被设置成 " ",单个空格。不过,我们可以方便地重新定义 OFS,这样 awk 将插入我们中意的字段分隔符。以下是原始 address.awk 程序的修订版,它使用 OFS 来输出那些中间的 ", " 字符串:
awk 还有一个特殊变量 ORS,全称是“输出记录分隔符”。通过设置缺省为换行 ("\n") 的 OFS,我们可以控制在 print 语句结尾自动打印的字符。缺省 ORS 值会使 awk 在新行中输出每个新的 print 语句。如果想使输出的间隔翻倍,可以将 ORS 设置成 "\n\n"。或者,如果想要用单个空格分隔记录(而不换行),将 ORS 设置成 ""。
假设我们编写了一个脚本,它将地址列表转换成每个记录一行,且用 tab
定界的格式,以便导入电子表格。使用稍加修改的 address.awk
之后,就可以清楚地看到这个程序只适合于三行的地址。如果 awk
遇到以下地址,将丢掉第四行,并且不打印该行:
要处理这种情况,代码最好考虑每个字段的记录数量,并依次打印每个记录。现在,代码只打印地址的前三个字段。以下就是我们想要的一些代码:
首先,将字段分隔符 FS 设置成 "\n",将记录分隔符 RS 设置成 "",这样 awk 可以象以前一样正确分析多行地址。然后,将输出记录分隔符 ORS 设置成 "",它将使 print 语句在每个调用结尾 不 输出新行。这意味着如果希望任何文本从新的一行开始,那么需要明确写入 print "\n" 。
在主代码块中,创建了一个变量 x 来存储正在处理的当前字段的编号。起初,它被设置成 1。然后,我们使用 while 循环(一种 awk 循环结构,等同于 C 语言中的 while 循环),对于所有记录(最后一个记录除外)重复打印记录和 tab 字符。最后,打印最后一个记录和换行;此外,由于将 ORS 设置成 "",print 将不输出换行。程序输出如下,这正是我们所期望的:
我们已经看到了 awk 的 while 循环结构,它等同于相应的 C 语言 while 循环。awk 还有 "do...while" 循环,它在代码块结尾处对条件求值,而不象标准 while 循环那样在开始处求值。它类似于其它语言中的 "repeat...until" 循环。以下是一个示例:
与一般的 while 循环不同,由于在代码块之后对条件求值,"do...while" 循环永远都至少执行一次。换句话说,当第一次遇到普通 while 循环时,如果条件为假,将永远不执行该循环。
for 循环
awk 允许创建 for 循环,它就象 while 循环,也等同于 C 语言的 for
循环:
以下是一个简短示例:
此段代码将打印:
iteration 1 iteration 2 iteration 3 iteration 4此外,如同 C 语言一样,awk 提供了 break 和 continue 语句。使用这些语句可以更好地控制 awk 的循环结构。以下是迫切需要 break 语句的代码片断:
因为 1 代表是真,这个 while 循环将永远运行下去。以下是一个只执行十次的循环:
这里,break 语句用于“逃出”最深层的循环。"break" 使循环立即终止,并继续执行循环代码块后面的语句。
continue 语句补充了 break,其作用如下:
这段代码打印 "iteration 1" 到 "iteration 21","iteration 4"
除外。如果迭代等于 4,则增加 x 并调用 continue 语句,该语句立即使
awk 开始执行下一个循环迭代,而不执行代码块的其余部分。如同 break
一样,continue 语句适合各种 awk 迭代循环。在 for
循环主体中使用时,continue
将使循环控制变量自动增加。以下是一个等价循环:
在 while 循环中时,在调用 continue 之前没有必要增加 x ,因为 for 循环会自动增加 x 。
如果您知道 awk 可以使用数组,您一定会感到高兴。然而,在 awk
中,数组下标通常从 1 开始,而不是 0:
awk 遇到第一个赋值语句时,它将创建 myarray ,并将元素 myarray[1] 设置成 "jim"。执行了第二个赋值语句后,数组就有两个元素了。
数组迭代
定义之后,awk 有一个便利的机制来迭代数组元素,如下所示:
这段代码将打印数组
myarray 中的每一个元素。当对于
for 使用这种特殊的 "in" 形式时,awk 将
myarray
的每个现有下标依次赋值给
x (循环控制变量),每次赋值以后都循环一次循环代码。虽然这是一个非常方便的
awk 功能,但它有一个缺点 -- 当 awk
在数组下标之间轮转时,它不会依照任何特定的顺序。那就意味着我们不能知道以上代码的输出是:
还是
套用 Forrest Gump 的话来说,迭代数组内容就像一盒巧克力 -- 您永远不知道将会得到什么。因此有必要使 awk 数组“字符串化”,我们现在就来研究这个问题。
在我的 前一篇文章 中,我演示了 awk 实际上以字符串格式来存储数字值。虽然 awk 要执行必要的转换来完成这项工作,但它却可以使用某些看起来很奇怪的代码:
a="1"执行了这段代码后, c 等于 6 。由于 awk 是“字符串化”的,添加字符串 "1" 和 "2" 在功能上并不比添加数字 1 和 2 难。这两种情况下,awk 都可以成功执行运算。awk 的“字符串化”性质非常可爱 -- 您可能想要知道如果使用数组的字符串下标会发生什么情况。例如,使用以下代码:
myarr["1"]="Mr. Whipple"可以预料,这段代码将打印 "Mr. Whipple"。但如果去掉第二个 "1" 下标中的引号,情况又会怎样呢?
myarr["1"]="Mr. Whipple"猜想这个代码片断的结果比较难。awk 将 myarr["1"] 和 myarr[1] 看作数组的两个独立元素,还是它们是指同一个元素?答案是它们指的是同一个元素,awk 将打印 "Mr. Whipple",如同第一个代码片断一样。虽然看上去可能有点怪,但 awk 在幕后却一直使用数组的字符串下标!
了解了这个奇怪的真相之后,我们中的一些人可能想要执行类似于以下的古怪代码:
myarr["name"]="Mr. Whipple"这段代码不仅不会产生错误,而且它的功能与前面的示例完全相同,也将打印 "Mr. Whipple"!可以看到,awk 并没有限制我们使用纯整数下标;如果我们愿意,可以使用字符串下标,而且不会产生任何问题。只要我们使用非整数数组下标,如 myarr["name"] ,那么我们就在使用 关联数组 。从技术上讲,如果我们使用字符串下标,awk 的后台操作并没有什么不同(因为即便使用“整数”下标,awk 还是会将它看作是字符串)。但是,应该将它们称作 关联数组 -- 它听起来很酷,而且会给您的上司留下印象。字符串化下标是我们的小秘密。;)
谈到数组时,awk 给予我们许多灵活性。可以使用字符串下标,而且不需要连续的数字序列下标(例如,可以定义 myarr[1] 和 myarr[1000] ,但不定义其它所有元素)。虽然这些都很有用,但在某些情况下,会产生混淆。幸好,awk 提供了一些实用功能有助于使数组变得更易于管理。
首先,可以删除数组元素。如果想要删除数组 fooarray 的元素 1 ,输入:
delete fooarray[1]而且,如果想要查看是否存在某个特定数组元素,可以使用特殊的 "in" 布尔运算符,如下所示:
if ( 1 in fooarray )虽然大多数情况下 awk 的 print 语句可以完成任务,但有时我们还需要更多。在那些情况下,awk 提供了两个我们熟知的老朋友 printf() 和 sprintf()。是的,如同其它许多 awk 部件一样,这些函数等同于相应的 C 语言函数。printf() 会将格式化字符串打印到 stdout,而 sprintf() 则返回可以赋值给变量的格式化字符串。如果不熟悉 printf() 和 sprintf(),介绍 C 语言的文章可以让您迅速了解这两个基本打印函数。在 Linux 系统上,可以输入 "man 3 printf" 来查看 printf() 帮助页面。
以下是一些 awk sprintf() 和 printf() 的样本代码。可以看到,它们几乎与 C 语言完全相同。
x=1此代码将打印:
Jim got a 83 on the last testawk 有许多字符串函数,这是件好事。在 awk 中,确实需要字符串函数,因为不能象在其它语言(如 C、C++ 和 Python)中那样将字符串看作是字符数组。例如,如果执行以下代码:
mystring="How are you doing today?"将会接收到一个错误,如下所示:
awk: string.gawk:59: fatal: attempt to use scalar as array噢,好吧。虽然不象 Python 的序列类型那样方便,但 awk 的字符串函数还是可以完成任务。让我们来看一下。
首先,有一个基本 length() 函数,它返回字符串的长度。以下是它的使用方法:
print length(mystring)此代码将打印值:
24好,继续。下一个字符串函数叫作 index,它将返回子字符串在另一个字符串中出现的位置,如果没有找到该字符串则返回 0。使用 mystring,可以按以下方法调用它:
print index(mystring,"you")awk 会打印:
9让我们继续讨论另外两个简单的函数,tolower() 和 toupper()。与您猜想的一样,这两个函数将返回字符串并且将所有字符分别转换成小写或大写。请注意,tolower() 和 toupper() 返回新的字符串,不会修改原来的字符串。这段代码:
print tolower(mystring)……将产生以下输出:
how are you doing today?awk 将打印:
you如果您通常用于编程的语言使用数组下标访问部分字符串(以及不使用这种语言的人),请记住 substr() 是 awk 代替方法。需要使用它来抽取单个字符和子串;因为 awk 是基于字符串的语言,所以会经常用到它。
现在,我们讨论一些更耐人寻味的函数,首先是 match()。match() 与 index() 非常相似,它与 index() 的区别在于它并不搜索子串,它搜索的是规则表达式。match() 函数将返回匹配的起始位置,如果没有找到匹配,则返回 0。此外,match() 还将设置两个变量,叫作 RSTART 和 RLENGTH。RSTART 包含返回值(第一个匹配的位置),RLENGTH 指定它占据的字符跨度(如果没有找到匹配,则返回 -1)。通过使用 RSTART、RLENGTH、substr() 和一个小循环,可以轻松地迭代字符串中的每个匹配。以下是一个 match() 调用示例:
print match(mystring,/you/), RSTART, RLENGTHawk 将打印:
9 9 3现在,我们将研究两个字符串替换函数,sub() 和 gsub()。这些函数与目前已经讨论过的函数略有不同,因为它们 确实修改原始字符串 。以下是一个模板,显示了如何调用 sub():
sub(regexp,replstring,mystring)调用 sub() 时,它将在 mystring 中匹配 regexp 的第一个字符序列,并且用 replstring 替换该序列。sub() 和 gsub() 用相同的自变量;唯一的区别是 sub() 将替换第一个 regexp 匹配(如果有的话),gsub() 将执行全局替换,换出字符串中的所有匹配。以下是一个 sub() 和 gsub() 调用示例:
sub(/o/,"O",mystring)必须将 mystring 复位成其初始值,因为第一个 sub() 调用直接修改了 mystring。在执行时,此代码将使 awk 输出:
HOw are you doing today?当然,也可以是更复杂的规则表达式。我把测试一些复杂规则表达式的任务留给您来完成。
通过介绍函数 split(),我们来汇总一下已讨论过的函数。split() 的任务是“切开”字符串,并将各部分放到使用整数下标的数组中。以下是一个 split() 调用示例:
numelements=split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",mymonths,",")调用 split() 时,第一个自变量包含要切开文字字符串或字符串变量。在第二个自变量中,应该指定 split() 将填入片段部分的数组名称。在第三个元素中,指定用于切开字符串的分隔符。split() 返回时,它将返回分割的字符串元素的数量。split() 将每一个片段赋值给下标从 1 开始的数组,因此以下代码:
print mymonths[1],mymonths[numelements]……将打印:
Jan Dec简短注释 -- 调用 length()、sub() 或 gsub() 时,可以去掉最后一个自变量,这样 awk 将对 $0(整个当前行)应用函数调用。要打印文件中每一行的长度,使用以下 awk 脚本:
{ print length() }几星期前,我决定用 awk 编写自己的支票簿结算程序。我决定使用简单的 tab 定界文本文件,以便于输入最近的存款和提款记录。其思路是将这个数据交给 awk 脚本,该脚本会自动合计所有金额,并告诉我余额。以下是我决定如何将所有交易记录到 "ASCII checkbook" 中:
23 Aug 2000 food - - Y Jimmy's Buffet 30.25此文件中的每个字段都由一个或多个 tab 分隔。在日期(字段 1,$1)之后,有两个字段叫做“费用分类帐”和“收入分类帐”。以上面这行为例,输入费用时,我在费用字段中放入四个字母的别名,在收入字段中放入 "-"(空白项)。这表示这一特定项是“食品费用”。:) 以下是存款的示例:
23 Aug 2000 - inco - Y Boss Man 2001.00在这个实例中,我在费用分类帐中放入 "-"(空白),在收入分类帐中放入 "inco"。"inco" 是一般(薪水之类)收入的别名。使用分类帐别名让我可以按类别生成收入和费用的明细分类帐。至于记录的其余部分,其它所有字段都是不需加以说明的。“是否付清?”字段("Y" 或 "N")记录了交易是否已过帐到我的帐户;除此之外,还有一个交易描述,和一个正的美元金额。
用于计算当前余额的算法不太难。awk 只需要依次读取每一行。如果列出了费用分类帐,但没有收入分类帐(为 "-"),那么这一项就是借方。如果列出了收入分类帐,但没有费用分类帐(为 "-"),那么这一项就是贷方。而且,如果同时列出了费用和收入分类帐,那么这个金额就是“分类帐转帐”;即,从费用分类帐减去美元金额,并将此金额添加到收入分类帐。此外,所有这些分类帐都是虚拟的,但对于跟踪收入和支出以及预算却非常有用。
现在该研究代码了。我们将从第一行(BEGIN 块和函数定义)开始:
首先执行 "chmod +x myscript" 命令,那么将第一行 "#!..." 添加到任何 awk 脚本将使它可以直接从 shell 中执行。其余行定义了 BEGIN 块,在 awk 开始处理支票簿文件之前将执行这个代码块。我们将 FS(字段分隔符)设置成 "\t+",它会告诉 awk 字段由一个或多个 tab 分隔。另外,我们定义了字符串 months,下面将出现的 monthdigit() 函数将使用它。
最后三行显示了如何定义自己的 awk 。格式很简单 -- 输入 "function",再输入名称,然后在括号中输入由逗号分隔的参数。在此之后,"{ }" 代码块包含了您希望这个函数执行的代码。所有函数都可以访问全局变量(如 months 变量)。另外,awk 提供了 "return" 语句,它允许函数返回一个值,并执行类似于 C 和其它语言中 "return" 的操作。这个特定函数将以 3 个字母字符串格式表示的月份名称转换成等价的数值。例如,以下代码:
print monthdigit("Mar")……将打印:
3现在,让我们讨论其它一些函数。
以下是其它三个执行簿记的函数。我们即将见到的主代码块将调用这些函数之一,按顺序处理支票簿文件的每一行,从而将相应交易记录到 awk 数组中。有三种基本交易,贷方 (doincome)、借方 (doexpense) 和转帐 (dotransfer)。您会发现这三个函数全都接受一个自变量,叫作 mybalance。mybalance 是二维数组的一个占位符,我们将它作为自变量进行传递。目前,我们还没有处理过二维数组;但是,在下面可以看到,语法非常简单。只须用逗号分隔每一维就行了。
我们将按以下方式将信息记录到 "mybalance" 中。数组的第一维从 0 到 12,用于指定月份,0 代表全年。第二维是四个字母的分类帐,如 "food" 或 "inco";这是我们处理的真实分类帐。因此,要查找全年食品分类帐的余额,应查看 mybalance[0,"food"]。要查找 6 月的收入,应查看 mybalance[6,"inco"]。
调用 doincome() 或任何其它函数时,我们将交易记录到两个位置 -- mybalance[0,category] 和 mybalance[curmonth, category],它们分别表示全年的分类帐余额和当月的分类帐余额。这让我们稍后可以轻松地生成年度或月度收入/支出明细分类帐。
如果研究这些函数,将发现在我的引用中传递了 mybalance 引用的数组。另外,我们还引用了几个全局变量:curmonth,它保存了当前记录所属的月份的数值,$2(费用分类帐),$3(收入分类帐)和金额($7,美元金额)。调用 doincome() 和其它函数时,已经为要处理的当前记录(行)正确设置了所有这些变量。
以下是主代码块,它包含了分析每一行输入数据的代码。请记住,由于正确设置了 FS,可以用 $ 1 引用第一个字段,用 $2 引用第二个字段,依次类推。调用 doincome() 和其它函数时,这些函数可以从函数内部访问 curmonth、$2、$3 和金额的当前值。请先研究代码,在代码之后可以见到我的说明。
在主块中,前两行将 curmonth 设置成 1 到 12 之间的整数,并将金额设置成字段 7(使代码易于理解)。然后,是四行有趣的代码,它们将值写到数组 globcat 中。globcat,或称作全局分类帐数组,用于记录在文件中遇到的所有分类帐 -- "inco"、"misc"、"food"、"util" 等。例如,如果 $2 == "inco",则将 globcat["inco"] 设置成 "yes"。稍后,我们可以使用简单的 "for (x in globcat)" 循环来迭代分类帐列表。
在接着的大约二十行中,我们分析字段 $2 和 $3,并适当记录交易。如果 $2=="-" 且 $3!="-",表示我们有收入,因此调用 doincome()。如果是相反的情况,则调用 doexpense();如果 $2 和 $3 都包含分类帐,则调用 dotransfer()。每次我们都将 "balance" 数组传递给这些函数,从而在这些函数中记录适当的数据。
您还会发现几行代码说“if ( $5 == "Y" ),那么将同一个交易记录到 balance2 中”。我们在这里究竟做了些什么?您将回忆起 $5 包含 "Y" 或 "N",并记录交易是否已经过帐到帐户。由于仅当过帐了交易时我们才将交易记录到 balance2,因此 balance2 包含了真实的帐户余额,而 "balance" 包含了所有交易,不管是否已经过帐。可以使用 balance2 来验证数据项(因为它应该与当前银行帐户余额匹配),可以使用 "balance" 来确保没有透支帐户(因为它会考虑您开出的尚未兑现的所有支票)。
主块重复处理了每一行记录之后,现在我们有了关于比较全面的、按分类帐和按月份划分的借方和贷方记录。现在,在这种情况下最合适的做法是只须定义生成报表的 END 块:
这个报表将打印出汇总,如下所示:
Your available funds: 1174.22 Your account balance: 2399.33在 END 块中,我们使用 "for (x in globcat)" 结构来迭代每一个分类帐,根据记录在案的交易结算主要余额。实际上,我们结算两个余额,一个是可用资金,另一个是帐户余额。要执行程序并处理您在文件 "mycheckbook.txt" 中输入的财务数据,将以上所有代码放入文本文件 "balance",执行 "chmod +x balance",然后输入 "./balance mycheckbook.txt"。然后 balance 脚本将合计所有交易,打印出两行余额汇总。
我使用这个程序的更高级版本来管理我的个人和企业财务。我的版本(由于篇幅限制不能在此涵盖)会打印出收入和费用的月度明细分类帐,包括年度总合、净收入和其它许多内容。它甚至以 HTML 格式输出数据,因此我可以在 Web 浏览器中查看它。:) 如果您认为这个程序有用,我建议您将这些特性添加到这个脚本中。不必将它配置成要 记录 任何附加信息;所需的全部信息已经在 balance 和 balance2 里面了。只要升级 END 块就万事具备了!
我希望您喜欢本系列。有关 awk 的详细信息,请参考以下列出的参考资料。
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是
Gentoo Technologies, Inc. 的总裁兼
CEO,
Gentoo Linux(用于 PC 的高级 Linux)和
Portage
系统(Linux 的下一代移植系统)的创始人。他还是 Macmillan 书籍
Caldera OpenLinux Unleashed、
SuSE Linux Unleashed 和
Samba Unleashed 的合作者。Daniel
自二年级起就与计算机结下不解之缘,那时他首先接触的是 Logo
程序语言,并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任
SONY
Electronic Publishing/Psygnosis
的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和新出生的女儿
Hadassah 一起共度时光。可通过
drobbins@gentoo.org 与 Daniel
联系。
chinaunix网友2010-12-22 14:36:44
很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com