--------------------------------------------------------------------------------
版权声明
Contents
Preface
Overview of AWK
Why AWK
How to get AWK.
How AWK works.
How to Compute and Print Certain Fields
Selection by Text Content and by Comparsion
Arrays in AWK
Making Shell Command in an AWK Program
A Pratical Example
Redirecting Output to Files
Using System Resources
Execute AWK Programs.
Changing Field Separator & User Define Functions
Using getline to Input file
Multi-line Record
Getting Argument on Command Line
Writing Interactive Program in AWK
Recursive Program
Appendix A Patterns
Appendix B Actions
Appendix C Built-in Funtions
Appendix D Built-in Variables
Appendix E Regular Expression
Alfred V. Aho, Brian W. Kernighan and Peter J. Weinberger,
The AWK Programming Language'',
Addison-Wesley Publishing Company
Dale Dougherty, " sed & awk '', O`Reilly & Associates, Inc
执行结果荧幕出现:
Jenny Work hours: 100 Pay: 21000
Dan Work hours: 110 Pay: 23650
Max Work hours: 130 Pay: 27170
John Work hours: 125 Pay: 27500
Linda Work hours: 95 Pay: 19950
编写程式 reformat4.awk 如下:
awk '
BEGIN {
Sys_Sort = "sort +0n >> today_rpt4"
Result = "today_rpt4"
# 改变栏位切割的方式
# 令 Shell执行``date''; getline 读取结果,并以$0纪录
FS = "[\t:]+"
"date" | getline
print " Today is " , $2, $3 >Result
print "=========================" > Result
print `` ID Number Arrival Time'' > Result
close( Result )
# 从档按中读取迟到资料, 并用阵列cnt[ ]记录. 阵列cnt[ ]中以
员工代号为# 注标, 所对应的值为该员工之迟到次数.
late_file = $2 "late.dat"
while( getline < late_file >0 ) cnt[$1] = $2
close( late_file )
}
{
# 已更改栏位切割方式, $2表小时数,$3表分钟数
arrival = HM_to_M($2, $3)
if( arrival > 480 ){
mark = "*" # 若当天迟到,应再增加其迟到次数, 且令
mark 为''*''.cnt[$1]++ }
else mark = " "
# message 用以显示该员工的迟到累计数, 若未曾迟到
message 为空字串
message = cnt[$1] ? cnt[$1] " times" : ""
printf("%s%2d:%2d %5s %s\n", $1, $2, $3, mark,
message ) | Sys_Sort
total += arrival
}
END {
close( Result )
close( Sys_Sort )
printf(" Average arrival time : %d:%d\n", total/NR/60,
(total/NR)%60 ) >> Result
#将阵列cnt[ ]中新的迟到资料写回档案中
for( any in cnt )
print any, cnt[any] > late_file
}
function HM_to_M( hour, min ){
return hour*60 + min
}
' $*
执行后, today_rpt4 之内容如下 :
Today is Aug 17
================================
ID Number Arrival Time
1005 8:12 * 1 times
1006 7:45 1 times
1008 8: 1 * 1 times
1012 7:46
1025 7:27 1 times
1028 7:49
1029 7:57 2 times
1034 7:26
1042 7:59
1051 7:51
1052 8: 5 * 3 times
1101 7:32
Average arrival time : 7:49
AWK 中除了函数的参数列(Argument List)上的参数(Arguments)外,
所有变数不管于何处出现全被视为 Global variable. 其生命持续
至程式结束 --- 该变数不论在function外或 function内皆可使用,
只要变数名称相同所使用的就是同一个变数,直到程式结束.
因 Recusive 函数内部的变数, 会因它呼叫子函数(本身)而重覆使用,
故撰写该类函数时, 应特别留心.
例如 : 执行
awk '
BEGIN
{
x = 35
y = 45
test_variable( x )
printf("Return to main : arg1= %d, x= %d, y= %d, z= %d\n",
arg1, x, y, z)
}
function test_variable( arg1 )
{
arg1++ # arg1 为参数列上的参数, 是local variable. 离开此函数
后将消失.
y ++ # 会改变主式中的变数 y
z = 55 # z 为该函数中新使用的变数, 主程式中变数 z 仍可被使用.
printf("Inside the function: arg1=%d,x=%d, y=%d, z=%d\n",
arg1, x, y, z)
} '
结果荧幕印出
Inside the function: arg1= 36,x= 35, y= 46, z= 55
Return to main : arg1= 0, x= 35, y= 46, z= 55
awk '
BEGIN {
Number = 123456789
Number = sprintf("$%.2f",Number)
while( match(Number,/[0-9][0-9][0-9][0-9]/ ) )
sub(/[0-9][0-9][0-9][.,]/, ",&", Number)
print Number
例:作为传送消息的机制之一,UNIX提供了一个向其所有用户传送消息的命令wall(意思是write to all写给所有用户),该命令允许向所有工作中的用户(终端)发送消息。为此,我们可以通过一段shell批处理程序wall.shell来模拟这一程序(事实上比较老的版本中wall就是一段shell批处理程序:
$cat wall.shell
:
# @(#) wall.shell:发送消息给每个已注册终端
#
cat >/tmp/$$
#用户录入消息文本 who -u | awk '{print $2}' | while read tty
do
cat /tmp/$$>$tty
done
V 变量 含义 缺省值
--------------------------------------------------------
N ARGC 命令行参数个数
G ARGIND 当前被处理文件的ARGV标志符
N ARGV 命令行参数数组
G CONVFMT 数字转换格式 %.6g
P ENVIRON UNIX环境变量
N ERRNO UNIX系统错误消息
G FIELDWIDTHS 输入字段宽度的空白分隔字符串
A FILENAME 当前输入文件的名字
P FNR 当前记录数
A FS 输入字段分隔符 空格
G IGNORECASE 控制大小写敏感0(大小写敏感)
A NF 当前记录中的字段个数
A NR 已经读出的记录数
A OFMT 数字的输出格式 %.6g
A OFS 输出字段分隔符 空格
A ORS 输出的记录分隔符 新行
A RS 输入的记录他隔符 新行
N RSTART 被匹配函数匹配的字符串首
N RLENGTH 被匹配函数匹配的字符串长度
N SUBSEP 下标分隔符 "34"
6.awk的内置函数
V 函数 用途或返回值
------------------------------------------------
N gsub(reg,string,target) 每次常规表达式reg匹配时替换target中的string
N index(search,string) 返回string中search串的位置
A length(string) 求串string中的字符个数
N match(string,reg) 返回常规表达式reg匹配的string中的位置
N printf(fORMat,variable) 格式化输出,按format提供的格式输出变量variable。
N split(string,store,delim) 根据分界符delim,分解string为store的数组元素
N sprintf(format,variable) 返回一个包含基于format的格式化数据,variables是要放到串中的数据
G strftime(format,timestamp) 返回一个基于format的日期或者时间串,timestmp是systime()函数返回的时间
N sub(reg,string,target) 第一次当常规表达式reg匹配,替换target串中的字符串
A substr(string,position,len) 返回一个以position开始len个字符的子串
P totower(string) 返回string中对应的小写字符
P toupper(string) 返回string中对应的大写字符
A atan(x,y) x的余切(弧度)
N cos(x) x的余弦(弧度)
A eXP(x) e的x幂
A int(x) x的整数部分
A log(x) x的自然对数值
N rand() 0-1之间的随机数
N sin(x) x的正弦(弧度)
A sqrt(x) x的平方根
A srand(x) 初始化随机数发生器。如果忽略x,则使用system()
G system() 返回自1970年1月1日以来经过的时间(按秒计算)
[ Last edited by 无奈何 on 2006-10-27 at 02:31 AM ]作者: 无奈何 时间: 2006-10-27 02:20 标题: GAWK 手册
档案'BBS-list':
aardvark 555-5553 1200/300 B
alpo-net 555-3412 2400/1200/300 A
barfly 555-7685 1200/300 A
bites 555-1675 2400/1200/300 A
camelot 555-0542 300 C
core 555-2912 1200/300 C
fooey 555-1234 2400/1200/300 B
foot 555-6699 1200/300 B
macfoo 555-6480 1200/300 A
sdace 555-3430 2400/1200/300 A
sabafoo 555-2127 1200/300 C
档案'shipped':
Jan 13 25 15 115
Feb 15 32 24 226
Mar 15 24 34 228
Apr 31 52 63 420
May 16 34 29 208
Jun 31 42 75 492
Jul 24 34 67 436
Aug 15 34 47 316
Sep 13 55 37 277
Oct 29 54 68 525
Nov 20 87 82 577
Dec 17 35 61 401
Jan 21 36 64 620
Feb 26 58 80 652
Mar 24 75 70 495
Apr 21 70 74 514
在 gawk 程式里面,控制叙述诸如 if、while 等控制程式执行的流
程。在 gawk 里的控制叙述与 C 的类似。
很多的控制叙述会包括其它的叙述,被包括的叙述称为 body。假
如 body 里面包括一个以上的叙述,必须以大括弧 { } 将这些叙述括起
来,而各个叙述之间需以换行(newline)或分号隔开。
7.1 if 叙述
if (condition) then-body [else else-body]
(p30 of
如果 condition 为真(true),则执行 then-body,否则执行 else-body。
举一个例子如下:
if (x % 2 == 0)
print "x is even"
else
print "x is odd"
7.2 while 叙述
while (condition)
body
while 叙述做的第一件事就是测试 condition。假如 condition 为真则
执行 body 的叙述。body 的叙述执行完後,会再测试 condition,假如
condition 为真,则 body 会再度被执行。这个过程会一直被重复直到
condition 不再是真。如果 condition 第一次测试就是伪(false),则
body 从没有被执行。
下面的例子会印出每个输入记录(record)的前三个栏位。
gawk '{ i=1
while (i <= 3) {
print $i
i++
}
}'
7.3 do-while 叙述
do
body
while (condition)
这个 do loop 执行 body 一次,然後只要 condition 是真则会重复执行 body。
(p32 of
即使开始时 condition 是伪,body 也会被执行一次。
下面的例子会印出每个输入记录十次。
gawk '{ i= 1
do {
print $0
i++
} while (i <= 10)
}'
gawk '# find smallest divisor of num
{ num=$1
for (div=2; div*div <=num; div++)
if (num % div == 0)
break
if (num % div == 0)
printf "Smallest divisor of %d is %d\n", num, div
else
printf "%d is prime\n", num }'
7.6 continue 叙述
(p34 of 46)
continue 叙述使用於 for、while、do-while 回圈内部,它会跳
过回圈 body 的剩馀部分,使得它立刻进行下一次回圈的执行。
下面的例子会印出 0 至 20 的全部数字,但是 5 并不会被印出。
gawk 'BEGIN {
for (x=0; x<=20; x++) {
if (x==5)
continue
printf ("%d",x)
}
print ""
}'
7.7 next 叙述、next file 叙述、exit 叙述
next 叙述强迫 gawk 立刻停止处理目前的记录(record)而继续下一
个记录。
next file 叙述类似 next。然而,它强迫 gawk 立刻停止处理目前
的资料档。
exit 叙述会使得 gawk 程式停止执行而跳出。然而,如果 END 出现
,它会去执行 END 的 actions。
[ Last edited by 无奈何 on 2006-10-27 at 02:42 AM ]作者: 无奈何 时间: 2006-10-27 02:20 标题: awk执行行操作及怎样从文本文件和字符串中抽取信息
转贴注:又是一篇找不到作者的好文。
[b]awk执行行操作及怎样从文本文件和字符串中抽取信息(一)[/b]
下面没有讲述a w k的全部特性,也不涉及a w k的深层次编程,仅讲述使用a w k执行行操作及怎样从文本文件和字符串中抽取信息。
引用:
内容有:
· 抽取域。
· 匹配正则表达式。
· 比较域。
· 向a w k传递参数。
· 基本的a w k行操作和脚本。
a w k语言的最基本功能是在文件或字符串中基于指定规则浏览和抽取信息。a w k抽取信息后,才能进行其他文本操作。完整的a w k脚本通常用来格式化文本文件中的信息。
下面没有讲述a w k的全部特性,也不涉及a w k的深层次编程,仅讲述使用a w k执行行操作及怎样从文本文件和字符串中抽取信息。
引用:
内容有:
· 抽取域。
· 匹配正则表达式。
· 比较域。
· 向a w k传递参数。
· 基本的a w k行操作和脚本。
a w k语言的最基本功能是在文件或字符串中基于指定规则浏览和抽取信息。a w k抽取信息后,才能进行其他文本操作。完整的a w k脚本通常用来格式化文本文件中的信息。
1 调用awk
有三种方式调用a w k,第一种是命令行方式,如:
代码:
awk [-F fild-separator] 'commands' input-file(s)
这里,c o m m a n d s是真正的a w k命令。
上面例子中, [ - F域分隔符]是可选的,因为a w k使用空格作为缺省的域分隔符,因此如果要浏览域间有空格的文本,不必指定这个选项,但如果要浏览诸如p a s s w d文件,此文件各域以冒号作为分隔符,则必须指明- F选项,如:
代码:
awk -F: 'commands' input-file(s)
第二种方法是将所有a w k命令插入一个文件,并使a w k程序可执行,然后用a w k命令解释器作为脚本的首行,以便通过键入脚本名称来调用它。
第三种方式是将所有的a w k命令插入一个单独文件,然后调用:
代码:
awk -f awk-script-file input-files(s)
- f选项指明在文件a w k _ s c r i p t _ f i l e中的a w k脚本, i n p u t _ f i l e ( s )是使用a w k进行浏览的文件名。
2 awk脚本
在命令中调用a w k时,a w k脚本由各种操作和模式组成。
如果设置了- F选项,则a w k每次读一条记录或一行,并使用指定的分隔符分隔指定域,但如果未设置- F选项,a w k假定空格为域分隔符,并保持这个设置直到发现一新行。当新行出现时,a w k命令获悉已读完整条记录,然后在下一个记录启动读命令,这个读进程将持续到文件尾或文件不再存在。
参照表,a w k每次在文件中读一行,找到域分隔符(这里是符号#),设置其为域n,直至一新行(这里是缺省记录分隔符),然后,划分这一行作为一条记录,接着a w k再次启动下一行读进程。
awk读文件记录的方式
引用:
域1 分隔符 域2 分隔符 域3 分隔符 域4及换行
P. B u n n y (记录1 ) # 0 2 / 9 9 # 4 8 # Yellow \n
J . Tr o l l (记录2 ) # 0 7 / 9 9 # 4 8 4 2 # Brown-3 \n
2.1 模式和动作
任何a w k语句都由模式和动作组成。在一个a w k脚本中可能有许多语句。模式部分决定动作语句何时触发及触发事件。处理即对数据进行的操作。如果省略模式部分,动作将时刻保持执行状态。
模式可以是任何条件语句或复合语句或正则表达式。模式包括两个特殊字段B E G I N和E N D。使用B E G I N语句设置计数和打印头。B E G I N语句使用在任何文本浏览动作之前,之后文本浏览动作依据输入文件开始执行。E N D语句用来在a w k完成文本浏览动作后打印输出文本总数和结尾状态标志。如果不特别指明模式, a w k总是匹配或打印行数。
实际动作在大括号{ }内指明。动作大多数用来打印,但是还有些更长的代码诸如i f和循环(l o o p i n g)语句及循环退出结构。如果不指明采取动作, a w k将打印出所有浏览出来的记录。
2. 域和记录
a w k执行时,其浏览域标记为$ 1,$ 2 . . . $ n。这种方法称为域标识。使用这些域标识将更容易对域进行进一步处理。
使用$ 1 , $ 3表示参照第1和第3域,注意这里用逗号做域分隔。如果希望打印一个有5个域的记录的所有域,不必指明$ 1 , $ 2 , $ 3 , $ 4 , $ 5,可使用$ 0,意即所有域。Aw k浏览时,到达一新行,即假定到达包含域的记录末尾,然后执行新记录下一行的读动作,并重新设置域分隔。
注意执行时不要混淆符号$和s h e l l提示符$,它们是不同的。
为打印一个域或所有域,使用p r i n t命令。这是一个a w k动作(动作语法用圆括号括起来)。
1. 抽取域
真正执行前看几个例子,现有一文本文件g r a d e . t x t,记录了一个称为柔道数据库的行信息。
代码:
$ cat grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28
第二种方法是使用t e e命令,在输出到文件的同时输出到屏幕。在测试输出结果正确与否时多使用这种方法。例如输出重定向到文件d e l e t e _ m e _ a n d _ d i e,同时输出到屏幕。使用这种方法,在a w k命令结尾写入| tee delete_me_and_die。
代码:
$ awk '{print }' grade.txt | tee delete_me_and_die
3. 使用标准输入
在深入讲解这一章之前,先对a w k脚本的输入方法简要介绍一下。实际上任何脚本都是从标准输入中接受输入的。为运行本章脚本,使用a w k脚本输入文件格式,例如:
引用:
belts.awk grade_student.txt
也可替代使用下述格式:
使用重定向方法:
belts.awk < grade2.txt
或管道方法:
grade2.txt | belts.awk
这里我怎么看不明白,汗
4. 打印所有记录
代码:
$ awk '{print }' grade.txt
a w k读每一条记录。因为没有模式部分,只有动作部分{print }(打印所有记录),这个动作必须用花括号括起来。上述命令打印整个文件。
5. 打印单独记录
假定只打印学生名字和腰带级别,通过查看域所在列,可知为f i e l d - 1和f i e l d - 4,因此可以使用$ 1和$ 4,但不要忘了加逗号以分隔域。
代码:
$ awk '{print ,}' grade.txt
M.Tans Green
J.Lulu green
P.Bunny Yellow
J.Troll Brown-3
L.Tansl Brown-2
6. 打印报告头
上述命令输出在名字和腰带级别之间用一些空格使之更容易划分,也可以在域间使用t a b键加以划分。为加入t a b键,使用t a b键速记引用符\ t,后面将对速记引用加以详细讨论。也可以为输出文本加入信息头。本例中加入n a m e和b e l t及下划线。下划线使用\ n,强迫启动新行,并在\ n下一行启动打印文本操作。打印信息头放置在B E G I N模式部分,因为打印信息头被界定为一个动作,必须用大括号括起来。在a w k查看第一条记录前,信息头被打印。
代码:
$ awk 'BEGIN {print "Name Belt\n-----------------------------------"}{print "\t",}' grade.txt
Name Belt
-----------------------------------
M.Tans Green
J.Lulu green
P.Bunny Yellow
J.Troll Brown-3
L.Tansl Brown-2
7. 打印信息尾
如果在末行加入end of report信息,可使用E N D语句。E N D语句在所有文本处理动作执行完之后才被执行。E N D语句在脚本中的位置放置在主要动作之后。下面简单打印头信息并告之查询动作完成。
代码:
$ awk 'BEGIN {print "Name\n--------"}{print } END ' grade.txt
Name
--------
M.Tans
J.Lulu
P.Bunny
J.Troll
L.Tansl
8. awk错误信息提示
几乎可以肯定,在使用a w k时,将会在命令中碰到一些错误。a w k将试图打印错误行,但由于大部分命令都只在一行,因此帮助不大。
系统给出的显示错误信息提示可读性不好。使用上述例子,如果丢了一个双引号, a w k将返回:
代码:
$ awk 'BEGIN {print "Name\n--------}{print } END ' grade.txt
awk: cmd. line:1: BEGIN {print "Name\n--------}{print } END
awk: cmd. line:1: ^ unterminated string
当第一次使用a w k时,可能被错误信息搅得不知所措,但通过长时间和不断的学习,可总结出以下规则。在碰到a w k错误时,可相应查找:
引用:
· 确保整个a w k命令用单引号括起来。
· 确保命令内所有引号成对出现。
· 确保用花括号括起动作语句,用圆括号括起条件语句。
· 可能忘记使用花括号,也许你认为没有必要,但a w k不这样认为,将按之解释语法
。
如果查询文件不存在,将得到下述错误信息:
代码:
$ awk 'END {print NR}' grades.txt
awk: cmd. line:2: fatal: cannot open file `grades.txt' for reading (没有那个文件或目录)
9.awk 键盘输入
如果在命令行并没有输入文件g r a d e . t x t,将会怎样?
代码:
$ awk 'BEGIN {print "Name\n--------"}{print } END '
Name
--------
B E G I N部分打印了文件头,但a w k最终停止操作并等待,并没有返回s h e l l提示符。这是因为a w k期望获得键盘输入。因为没有给出输入文件, a w k假定下面将会给出。如果愿意,顺序输入相关文本,并在输入完成后敲<Ct r l - D >键。如果敲入了正确的域分隔符, a w k会像第一个例子一样正常处理文本。这种处理并不常用,因为它大多应用于大量的打印稿。
2.3awk中正则表达式及其操作
在g r e p一章中,有许多例子用到正则表达式,这里将不使用同样的例子,但可以使用条件操作讲述a w k中正则表达式的用法。
这里正则表达式用斜线括起来。例如,在文本文件中查询字符串G r e e n,使用/ G r e e n /可以查出单词G r e e n的出现情况。
在从文件中抽取信息时,最好首先检查文件中是否有记录。下面的例子只有在文件中至少有一个记录时才查询B r o w n级别记录。使用A N D复合语句实现这一功能。意即至少存在一个记录后,查询字符串B r o w n,最后打印结果。
代码:
[root@chenwy sam]# awk '{if (NR>0 && ~/Brown/)print }' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28
N F的一个强大功能是将变量$ P W D的返回值传入a w k并显示其目录。这里需要指定域分隔符/。
代码:
[root@chenwy sam]# echo $PWD | awk -F/ ' {print $NF}'
sam
1. 设置输入域到域变量名
在a w k中,设置有意义的域名是一种好习惯,在进行模式匹配或关系操作时更容易理解。
一般的变量名设置方式为n a m e = $ n,这里n a m e为调用的域变量名, n为实际域号。例如设置学生域名为n a m e,级别域名为b e l t,操作为n a m e = $ 1 ; b e l t s = $ 4。注意分号的使用,它分隔a w k命令。下面例子中,重新赋值学生名域为n a m e,级别域为b e l t s。查询级别为Ye l l o w的记录,并最终打印名称和级别。
代码:
[sam@chenwy sam]$ awk '{name=;belts=;if(belts ~/Yellow/) print name" is belt "belts}' grade.txt
P.Bunny is belt Yellow
2. 域值比较操作
有两种方式测试一数值域是否小于另一数值域。
1) 在B E G I N中给变量名赋值。
2) 在关系操作中使用实际数值。
通常在B E G I N部分赋值是很有益的,可以在a w k表达式进行改动时减少很多麻烦。
使用关系操作必须用圆括号括起来。
下面的例子查询所有比赛中得分在2 7点以下的学生。
用引号将数字引用起来是可选的,“2 7”、2 7产生同样的结果。
代码:
[sam@chenwy sam]$ awk '{if (<) print }' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
第二个例子中给数字赋以变量名B A S E L I N E和在B E G I N部分给变量赋值,两者意义相同。
代码:
[sam@chenwy sam]$ awk 'BEGIN {if (J.Lulu 06/99 48317 green 9 24 26
J.Troll 07/99 4842 Brown-3 12 26 26
3. 修改数值域取值
当在a w k中修改任何域时,重要的一点是要记住实际输入文件是不可修改的,修改的只是保存在缓存里的a w k复本。a w k会在变量N R或N F变量中反映出修改痕迹。
为修改数值域,简单的给域标识重赋新值,如: $ 1 = $ 1 + 5,会将域1数值加5,但要确保赋值域其子集为数值型。
修改M . Ta n s l e y的目前级别分域,使其数值从4 0减为3 9,使用赋值语句$ 6 = $ 6 - 1,当然在实施修改前首先要匹配域名。
代码:
[sam@chenwy sam]$ awk '{if(=="M.Tans") ;print ,,}' grade.txt
M.Tans 39 44
J.Lulu 24 26
P.Bunny 35 28
J.Troll 26 26
L.Tansl 30 28
4. 修改文本域
修改文本域即对其重新赋值。需要做的就是赋给一个新的字符串。在J . Tr o l l中加入字母,使其成为J . L . Tr o l l,表达式为$ 1 = " J . L . Tr o l l ",记住字符串要使用双秒号( " "),并用圆括号括起整个语法。
代码:
[sam@chenwy sam]$ awk '{if(=="J.Troll") ="J.L.Troll"; print }' grade.txt
M.Tans
J.Lulu
P.Bunny
J.L.Troll
L.Tansl
6. 创建新的输出域
在a w k中处理数据时,基于各域进行计算时创建新域是一种好习惯。创建新域要通过其他域赋予新域标识符。如创建一个基于其他域的加法新域{ $ 4 = $ 2 + $ 3 },这里假定记录包含3个域,则域4为新建域,保存域2和域3相加结果。
在文件g r a d e . t x t中创建新域8保存域目前级别分与域最高级别分的减法值。表达式为‘{ $ 8 = $ 7 - $ 6 }’,语法首先测试域目前级别分小于域最高级别分。新域因此只打印其值大于零的学生名称及其新域值。在B E G I N部分加入t a b键以对齐报告头。
代码:
[sam@chenwy sam]$ awk 'BEGIN{print "Name\tDifference"}{if(<) {=-;print ,}}' grade.txt
Name Difference
M.Tans 4
J.Lulu 2
7. 增加列值
为增加列数或进行运行结果统计,使用符号+ =。增加的结果赋给符号左边变量值,增加到变量的域在符号右边。例如将$ 1加入变量t o t a l,表达式为t o t a l + = $ 1。列值增加很有用。许多文件都要求统计总数,但输出其统计结果十分繁琐。在a w k中这很简单,请看下面的例子。
将所有学生的‘目前级别分’加在一起,方法是t o t + = $ 6,t o t即为a w k浏览的整个文件的域6结果总和。所有记录读完后,在E N D部分加入一些提示信息及域6总和。不必在a w k中显示说明打印所有记录,每一个操作匹配时,这是缺省动作。
代码:
[sam@chenwy sam]$ awk '(tot+=); END{print "Club student total points :" tot}'
grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28
Club student total points :155
如果文件很大,你只想打印结果部分而不是所有记录,在语句的外面加上圆括号()即可。
代码:
[sam@chenwy sam]$ awk '; END{print "Club student total points :" tot}' grade.txt
Club student total points :155
8. 文件长度相加
在目录中查看文件时,如果想快速查看所有文件的长度及其总和,但要排除子目录,使用ls -l命令,然后管道输出到a w k,a w k首先剔除首字符为d(使用正则表达式)的记录,然后将文件长度列相加,并输出每一文件长度及在E N D部分输出所有文件的长度。
本例中,首先用ls -l命令查看一下文件属性。注意第二个文件属性首字符为d,说明它是一个目录,文件长度是第5列,文件名是第9列。如果系统不是这样排列文件名及其长度,应适时加以改变。
下面的正则表达式表明必须匹配行首,并排除字符d,表达式为^ [ ^ d ]。
使用此模式打印文件名及其长度,然后将各长度相加放入变量t o t中。
代码:
[sam@chenwy sam]$ ls -l | awk '/^[^d]/ {print "\t"} END {print "total KB:" tot}'
...................
total KB:174144
内置的字符串函数
代码:
awk内置字符串函数
g s u b ( r, s ) 在整个$ 0中用s替代r
g s u b ( r, s , t ) 在整个t中用s替代r
i n d e x ( s , t ) 返回s中字符串t的第一位置
l e n g t h ( s ) 返回s长度
m a t c h ( s , r ) 测试s是否包含匹配r的字符串
s p l i t ( s , a , f s ) 在f s上将s分成序列a
s p r i n t ( f m t , e x p ) 返回经f m t格式化后的e x p
s u b ( r, s ) 用$ 0中最左边最长的子串代替s
s u b s t r ( s , p ) 返回字符串s中从p开始的后缀部分
s u b s t r ( s , p , n ) 返回字符串s中从p开始长度为n的后缀部分
g s u b函数有点类似于s e d查找和替换。它允许替换一个字符串或字符为另一个字符串或字符,并以正则表达式的形式执行。第一个函数作用于记录$ 0,第二个g s u b函数允许指定目标,然而,如果未指定目标,缺省为$ 0。
i n d e x(s,t)函数返回目标字符串s中查询字符串t的首位置。l e n g t h函数返回字符串s字符长度。
m a t c h函数测试字符串s是否包含一个正则表达式r定义的匹配。s p l i t使用域分隔符f s将字符串s划分为指定序列a。
s p r i n t函数类似于p r i n t f函数(以后涉及),返回基本输出格式f m t的结果字符串e x p。
s u b(r,s)函数将用s替代$ 0中最左边最长的子串,该子串被( r)匹配。
s u b(s,p)返回字符串s在位置p后的后缀。s u b s t r(s,p,n)同上,并指定子串长度为n。
现在看一看a w k中这些字符串函数的功能。
2. index
查询字符串s中t出现的第一位置。必须用双引号将字符串括起来。例如返回目标字符串B u n n y中n y出现的第一位置,即字符个数。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN {print index("Bunny","ny")}' grade.txt
4
3. length
返回所需字符串长度,例如检验字符串J . Tr o l l返回名字及其长度,即人名构成的字符个数
代码:
[root@Linux_chenwy sam]# awk '=="J.Troll" {print length()" "}' grade.txt
7 J.Troll
还有一种方法,这里字符串加双引号。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN{print length("A FEW GOOD MEN")}'
14
4. match
m a t c h测试目标字符串是否包含查找字符的一部分。可以对查找部分使用正则表达式,返回值为成功出现的字符排列数。如果未找到,返回0,第一个例子在A N C D中查找d。因其不存在,所以返回0。第二个例子在A N C D中查找D。因其存在,所以返回A N C D中D出现的首位置字符数。第三个例子在学生J . L u l u中查找u。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN{print match("ANCD",/d/)}'
0
[root@Linux_chenwy sam]# awk 'BEGIN{print match("ANCD",/D/)}'
4
[root@Linux_chenwy sam]# awk '=="J.Lulu" {print match(,"u")}' grade.txt
4
5. split
使用s p l i t返回字符串数组元素个数。工作方式如下:如果有一字符串,包含一指定分隔符- ,例如A D2 - K P 9 - J U 2 - L P - 1,将之划分成一个数组。使用s p l i t,指定分隔符及数组名。此例中,命令格式为( " A D 2 - K P 9 - J U 2 - L P - 1 ",p a r t s _ a r r a y," - "),s p l i t然后返回数组下标数,这里结果为4。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN {print split("123-456-789",pats_array,"-")}'3
这个例子中,s p l i t返回数组m y a r r a y的下标数。数组m y a r r a y取值如下:
代码:
myarray[1]=123
myarray[2]=456
myarray[3]=789
结尾部分讲述数组概念。
6. sub
使用s u b发现并替换模式的第一次出现位置。字符串S T R包含‘poped popo pill’,执行下列s u b命令s u b(/ o p /," o p ",S T R)。模式o p第一次出现时,进行替换操作,返回结果如下:‘pO Ped pope pill’。
如:学生J . Tr o l l的记录有两个值一样,“目前级别分”与“最高级别分”。只改变第一个为2 9,第二个仍为2 4不动,操作命令为s u b(/ 2 6 /," 2 9 ",$ 0),只替换第一个出现2 4的位置。注意J . Tr o l l记录需存在。
代码:
[root@Linux_chenwy sam]# awk '=="J.Troll" sub(/26/,"29",)' grade.txt
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 29
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 29 26
L.Tansl 05/99 4712 Brown-2 12 30 28
7. substr
s u b s t r是一个很有用的函数。它按照起始位置及长度返回字符串的一部分。例子如下:
代码:
[root@Linux_chenwy sam]# awk '=="L.Tansl" {print substr(,1,3)}' grade.txt
L.T
上面例子中,指定在域1的第一个字符开始,返回其前面5个字符。
如果给定长度值远大于字符串长度, a w k将从起始位置返回所有字符,要抽取L Ta n s l - e y的姓,只需从第3个字符开始返回长度为7。可以输入长度9 9,a w k返回结果相同。
代码:
[root@Linux_chenwy sam]# awk '=="L.Tansl" {print substr(,1,99)}' grade.txt
L.Tansl
s u b s t r的另一种形式是返回字符串后缀或指定位置后面字符。这里需要给出指定字符串及其返回字串的起始位置。例如,从文本文件中抽取姓氏,需操作域1,并从第三个字符开始:
代码:
[root@Linux_chenwy sam]# awk '{print substr(,3)}' grade.txt
Tans
Lulu
Bunny
Troll
Tansl
还有一个例子,在B E G I N部分定义字符串,在E N D部分返回从第t个字符开始抽取的子串。
代码:
[root@Linux_chenwy sam]# awk 'BEGIN{STR="A FEW GOOD MEN"}END{print substr(STR,7)}' grade.txt
GOOD MEN
8. 从s h e l l中向a w k传入字符串
a w k脚本大多只有一行,其中很少是字符串表示的。大多要求在一行内完成a w k脚本,这一点通过将变量传入a w k命令行会变得很容易。现就其基本原理讲
述一些例子。
使用管道将字符串s t a n d - b y传入a w k,返回其长度。
代码:
[root@Linux_chenwy sam]# echo "Stand-by" | awk '{print length()}'
8
注意,\ 1 0 4为D的八进制A S C I I码,\ 1 4 1为a的八进制A S C I I码,等等。
awk输出函数printf
目前为止,所有例子的输出都是直接到屏幕,除了t a b键以外没有任何格式。a w k提供函数p r i n t f,拥有几种不同的格式化输出功能。例如按列输出、左对齐或右对齐方式。
每一种p r i n t f函数(格式控制字符)都以一个%符号开始,以一个决定转换的字符结束.转换包含三种修饰符。
p r i n t f函数基本语法是p r i n t f([格式控制符],参数),格式控制字符通常在引号里。
printf修饰符
代码:
- 左对齐
Wi d t h 域的步长,用0表示0步长
. p r e c 最大字符串长度,或小数点右边的位数
表9-7 awk printf格式
% c A S C I I字符
% d 整数
% e 浮点数,科学记数法
% f 浮点数,例如(1 2 3 . 4 4)
% g a w k决定使用哪种浮点数转换e或者f
% o 八进制数
% s 字符串
% x 十六进制数
1. 字符转换
观察A S C I I码中6 5的等价值。管道输出6 5到a w k。p r i n t f进行A S C I I码字符转换。这里也加入换行,因为缺省情况下p r i n t f不做换行动作。
代码:
A[sam@chenwy sam]$ echo "65" | awk '{printf "%c\n",}'
A
按同样方式使用a w k得到同样结果。
代码:
[sam@chenwy sam]$ awk 'BEGIN{printf "%c\n",65}'
A
加入一些文本注释帮助理解报文含义。可在正文前嵌入头信息。注意这里使用p r i n t加入头信息。如果愿意,也可使用p r i n t f。
代码:
[root@chenwy sam]# awk 'BEGIN{print "Name\t\tS.Number"}{printf "%-15s %s\n",,}' grade.txt
Name S.Number
M.Tans 48311
J.Lulu 48317
P.Bunny 48
J.Troll 4842
L.Tansl 4712
3.向一行a w k命令传值
在查看a w k脚本前,先来查看怎样在a w k命令行中传递变量。
在a w k执行前将值传入a w k变量,需要将变量放在命令行中,格式如下:
代码:
awk 命令变量=输入文件值
(后面会讲到怎样传递变量到a w k脚本中)。
下面的例子在命令行中设置变量A G E等于1 0,然后传入a w k中,查询年龄在1 0岁以下的所有学生。
代码:
[root@chenwy sam]# awk '{if (M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
要快速查看文件系统空间容量,观察其是否达到一定水平,可使用下面a w k一行脚本。因为要监视的已使用空间容量不断在变化,可以在命令行指定一个触发值。首先用管道命令将df -k 传入a w k,然后抽出第4列,即剩余可利用空间容量。使用$ 4 ~ / ^ [ 0 - 9 ] /取得容量数值(1 0 2 4块)而不是d f的文件头,然后对命令行与‘ i f ( $ 4 < T R I G G E R )’上变量T R I G G E R中指定
的值进行查询测试。
代码:
[root@chenwy sam]# df -k|awk '{if(/boot 458589
/dev/shm 99352
如果系统中d f输出格式不同,必须相应改变列号以适应工作系统。
当然可以使用管道将值传入a w k。本例使用w h o命令, w h o命令第一列包含注册用户名,这里打印注册用户,并加入一定信息。
代码:
[sam@chenwy sam]$ who |awk '{print " is logged on"}'
root is logged on
root is logged on
[sam@chenwy sam]$ who
root :0 Nov 23 20:17
root pts/0 Nov 23 20:25 (:0.0)
a w k也允许传入环境变量。下面的例子使用环境变量HOME支持当前用户目录。可从pwd命令管道输出到a w k中获得相应信息。
代码:
[sam@chenwy sam]$ pwd | awk '{if (==derr) print }' derr=$HOME
/usr/sam
4. awk脚本文件
可以将a w k脚本写入一个文件再执行它。命令不必很长(尽管这是写入一个脚本文件的主要原因),甚至可以接受一行命令。这样可以保存a w k命令,以使不必每次使用时都需要重新输入。使用文件的另一个好处是可以增加注释,以便于理解脚本的真正用途和功能。
使用前面的几个例子,将之转换成a w k可执行文件。像原来做的一样,将学生目前级别分相加awk ‘(t o t + = $ 6) END{print "club student total points:" t o t }’ g r a d e . t x t。
创建新文件s t u d e n t _ t o t . a w k,给所有a w k程序加入a w k扩展名是一种好习惯,这样通过查看文件名就知道这是一个a w k程序。文本如下:
代码:
[sam@chenwy sam]$ cat student_tot.awk
#!/bin/awk -f
#all commnet lines must start with a hash '#'
#name:students_tots.awk
#to call:student_tot.awk grade.txt
#prints total and average of club student points
#print a header first
BEGIN{
print "Student Date Member No. Grade Age Points Max"
print "Name Joined Gained Point Available"
print "=============================================================="
}
#let's add the scores of points gained
(tot+=)
#finished proessing now let's print the total and average point
END{
print "Club student total points :" tot
print "Average Club Student Points:" tot/NR}
通过将命令分开,脚本可读性提高,还可以在命令之间加入注释。这里加入头
信息和结尾的平均值。基本上这是一个一行脚本文件。
执行时,在脚本文件后键入输入文件名,但是首先要对脚本文件加入可执行权限。
代码:
[sam@chenwy sam]$ chmod u+x student_tot.awk
[sam@chenwy sam]$./student_tot.awk grade.txt
Student Date Member No. Grade Age Points Max
Name Joined Gained Point Available
==============================================================
M.Tans 5/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansl 05/99 4712 Brown-2 12 30 28
Club student total points :155
Average Club Student Points:31
现在用a w k脚本过滤出错误行的出现频率,使得每一个失败记录只对应一个错误行。awk脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat error_strip.awk
#!/bin/awk -f
#error_strip.awk
#to call:error_strip.awk
#strips out the ERROR* lines if there are more than one
#ERROR* lines after each failed record.
BEGIN
#tell awk the whole is "ERROR*"
{if (=="ERROR*" && error_line=="ERROR*")
5. 在a w k中使用F S变量
如果使用非空格符做域分隔符( F S)浏览文件,例如# 或:,编写这样的一行命令很容易,因为使用F S选项可以在命令行中指定域分隔符。
代码:
$awk -F: '{print }' inputfile
使用a w k脚本时,记住设置F S变量是在B E G I N部分。如果不这样做, a w k将会发生混淆,不知道域分隔符是什么。
下述脚本指定F S变量。脚本从/ e t c / p a s s w d文件中抽取第1和第5域,通过分号“;”分隔p a s s w d文件域。第1域是帐号名,第5域是帐号所有者。
我举的例子是第七个域:
6. 向a w k脚本传值
向a w k脚本传值与向a w k一行命令传值方式大体相同,格式为:
代码:
awk script_file var=value input_file
下述脚本对比检查文件中域号和指定数字。这里使用了N F变量M A X,表示指定检查的域号,使用双引号将域分隔符括起来,即使它是一个空格。
脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat fieldcheck.awk
#!/bin/awk -f
#check on how many fields in a file
#name:fieldcheck.awk
#to call:fieldcheck MAX=n FS= filename
#
NF!=MAX{
print("line" NR " does not have " MAX "fields")}
如果NF中的值不等于最大MAX值,则打印出"哪一行的域总数不是max"
如果以/ e t c / p a s s w d作输入文件(p a s s w d文件有7个域),运行上述脚本。参数格式如下:
代码:
[sam@Linux_chenwy sam]$ chmod u+x fieldcheck.awk
[sam@Linux_chenwy sam]$ ./fieldcheck.awk MAX=7 FS=":" passwd
正好7个域,如果改成6,就会显示不同结果,试试看?
使用前面一行脚本的例子,将之转换成a w k脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat name.awk
#!/bin/awk -f
#name:age.awk
#to call:age.awk AGE=n grade.txt
#print ages that are lower than the age supplied on the comand line
{if (print }
数组
前面讲述s p l i t函数时,提到怎样使用它将元素划分进一个数组。这里还有一个例子:
代码:
[sam@Linux_chenwy sam]$ awk 'BEGIN {print split("123#456#789",myarray,"#")}'
3
实际上m y a r r a y数组为
代码:
Myarray[1]="123"
Myarray[2]="456"
Myarray[3]="789"
数组使用前,不必定义,也不必指定数组元素个数。经常使用循环来访问数组。下面是一种循环类型的基本结构:
代码:
For (element in array ) print array[element]
对于记录“ 1 2 3 # 4 5 6 # 6 7 8”,先使用s p l i t函数划分它,再使用循环打印各数组元素。操作脚本如下:
代码:
[sam@Linux_chenwy sam]$ cat arraytest.awk
#!/bin/awk -f
#name:arraytest.awk
#prints out an array
BEGIN{
record="123#456#789";
split(record,myarray,"#")}
END{for (i in myarray) {print myarray[i]}}
要运行脚本,使用/ d e v / n u l l作为输入文件。
代码:
sam@Linux_chenwy sam]$chmod u+x arraytest.awk
[sam@Linux_chenwy sam]$ ./arraytest.awk /dev/null
123
456
789
[sam@Linux_chenwy sam]$
数组和记录
上面的例子讲述怎样通过s p l i t函数使用数组。也可以预先定义数组,并使用它与域进行比较测试,下面的例子中将使用更多的数组。
下面是从空手道数据库卸载的一部分数据,包含了学生级别及是否是成人或未成年人的信息,有两个域,分隔符为( #),文件如下:
代码:
[sam@Linux_chenwy sam]$ cat grade_student.txt
Yellow#Junior
Orange#Senior
Yellor#Junior
Purple#Junior
Brown-2#Junior
White#Senior
Orange#Senior
Red#Junior
Red#Junior
Brown-2#Senior
Yellow#Senior
Red#Junior
Blue#Senior
Green#Senior
Purple#Junior
White#Junior
脚本功能是读文件并输出下列信息。
1) 俱乐部中Ye l l o w、O r a n g e和R e d级别的人各是多少。
2 ) 俱乐部中有多少成年人和未成年人。
查看文件,也许2 0秒内就会猜出答案,但是如果记录超过6 0个又怎么办呢?这不会很容易就看出来,必须使用a w k脚本。
首先看看a w k脚本,然后做进一步讲解。
代码:
[sam@Linux_chenwy sam]$ cat belts.awk
#!/bin/awk -f
#name:belts.awk
#to call:belts.awk grade2.txt
#loops through the grade2.txt file and counts how many
#belts we have in (yellow,orange,red)
#also count how many adults and juniors we have
#
#start of BEGIN
#set FS and load the arrays with our values
#B E G I N部分设置F S为符号#,即域分隔符
BEGIN{FS="#"
#Load the belt colours we are interested in only
#因为要查找Ye l l o w、O r a n g e和R e d三个级别。
#然后在脚本中手工建立数组下标对学生做同样的操作。
#注意,脚本到此只有下标或元素,并没有给数组名本身加任何注释。
belt["Yellow"]
belt["Orange"]
belt["Red"]
#end of BEGIN
#load the student type
student["Junior"]
student["Senior"]
}
##初始化完成后, B E G I N部分结束。记住B E G I N部分并没有文件处理操作。
#loop thru array that holds the belt colours against field-1
#if we have a match,keep a running total
#现在可以处理文件了。
#首先给数组命名为c o l o r,使用循环语句测试域1级别列是否
#等于数组元素之一(Ye l l o w、O r a n g e或R e d),
#如果匹配,依照匹配元素将运行总数保存进数组。
{for (colour in belt)
{if(==colour)
belt[colour]++}}
#loop thru array that holds the student type against
#field-2 if we have a match,keep a runing total
#同样处理数组‘ S e n i o r _ o r _ j u n i o r’,
#浏览域2时匹配操作满足,运行总数存入j u n i o r或s e n i o r的匹配数组元素。
{for (senior_or_junior in student)
{if (==senior_or_junior)
student[senior_or_junior]++}}
#finished processing so print out the matches..for each array
#E N D部分打印浏览结果,对每一个数组使用循环语句并打印它。
END{for (colour in belt )print "The club has ",belt[colour],colour,"Belts"
#注意在打印语句末尾有一个\符号,用来通知a w k(或相关脚本)命令持续到下一行,
#当输入一个很长的命令,并且想分行输入时可使用这种方法。
for (senior_or_junior in student) print "The club has ",\
student[senior_or_junior],senior_or_junior,"student"}
运行脚本前记住要加入可执行权限
代码:
[sam@Linux_chenwy sam]$ chmod u+x belts.awk
[sam@Linux_chenwy sam]$ ./belts.awk grade_student.txt
The club has 3 Red Belts
The club has 2 Orange Belts
The club has 2 Yellow Belts
The club has 7 Senior student
The club has 9 Junior student
[[i] Last edited by 无奈何 on 2006-10-27 at 02:55 AM [/i]]作者: 无奈何 时间: 2006-10-27 02:20 占一楼作者: 无奈何 时间: 2006-10-27 02:20 标题: 通用线程:Awk 实例
BEGIN 和 END 块
通常,对于每个输入行,awk 都会执行每个脚本代码块一次。然而,在许多编程情况中,可能需要在 awk 开始处理输入文件中的文本之 前 执行初始化代码。对于这种情况,awk 允许您定义一个 BEGIN 块。我们在前一个示例中使用了 BEGIN 块。因为 awk 在开始处理输入文件之前会执行 BEGIN 块,因此它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。
awk 还提供了另一个特殊块,叫作 END 块。awk 在处理了输入文件中的所有行之后执行这个块。通常,END 块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
表达式和块
还有许多其它方法可以选择执行代码块。我们可以将任意一种布尔表达式放在一个代码块之前,以控制何时执行某特定块。仅当对前面的布尔表达式求值为真时,awk 才执行代码块。以下示例脚本输出将输出其第一个字段等于 fred 的所有行中的第三个字段。如果当前行的第一个字段不等于 fred ,awk 将继续处理文件而不对当前行执行 print 语句:
在主代码块中,创建了一个变量 x 来存储正在处理的当前字段的编号。起初,它被设置成 1。然后,我们使用 while 循环(一种 awk 循环结构,等同于 C 语言中的 while 循环),对于所有记录(最后一个记录除外)重复打印记录和 tab 字符。最后,打印最后一个记录和换行;此外,由于将 ORS 设置成 "",print 将不输出换行。程序输出如下,这正是我们所期望的:
我们想要的输出。不算漂亮,但用 tab 定界,以便于导入电子表格
Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345
Big Tony 200 Incognito Ave. Suburbia, WA 67890
Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543
循环结构
我们已经看到了 awk 的 while 循环结构,它等同于相应的 C 语言 while 循环。awk 还有 "do...while" 循环,它在代码块结尾处对条件求值,而不象标准 while 循环那样在开始处求值。它类似于其它语言中的 "repeat...until" 循环。以下是一个示例:
do...while 示例
{
count=1
do {
print "I get printed at least once no matter what"
} while ( count != 1 )
}
与一般的 while 循环不同,由于在代码块之后对条件求值,"do...while" 循环永远都至少执行一次。换句话说,当第一次遇到普通 while 循环时,如果条件为假,将永远不执行该循环。
for 循环
awk 允许创建 for 循环,它就象 while 循环,也等同于 C 语言的 for 循环:
#record all the categories encountered
if ( $2 != "-" )
globcat[$2]="yes"
if ( $3 != "-" )
globcat[$3]="yes"
#tally up the transaction properly
if ( $2 == "-" ) {
if ( $3 == "-" ) {
print "Error: inc and exp fields are both blank!"
exit 1
} else {
#this is income
doincome(balance)
if ( $5 == "Y" )
doincome(balance2)
}
} else if ( $3 == "-" ) {
#this is an expense
doexpense(balance)
if ( $5 == "Y" )
doexpense(balance2)
} else {
#this is a transfer
dotransfer(balance)
if ( $5 == "Y" )
dotransfer(balance2)
}
}
生成报表
主块重复处理了每一行记录之后,现在我们有了关于比较全面的、按分类帐和按月份划分的借方和贷方记录。现在,在这种情况下最合适的做法是只须定义生成报表的 END 块:
balance,第 4 部分
END {
bal=0
bal2=0
for (x in globcat) {
bal=bal+balance[0,x]
bal2=bal2+balance2[0,x]
}
printf("Your available funds: %10.2f\n", bal)
printf("Your account balance: %10.2f\n", bal2)
}
这个报表将打印出汇总,如下所示:
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 块就万事具备了!