zz 通用线程:Awk 实例,第 3部分
2011-11-27 18:44
218 查看
通用线程:Awk 实例,第 3部分
字符串函数和……支票簿?Daniel Robbins (drobbins@gentoo.org), 总裁兼 CEO, Gentoo Technologies, Inc.
简介: 在这篇 awk 系列的总结中,Daniel 向您介绍 awk 重要的字符串函数,以及演示了如何从头开始编写完整的支票簿结算程序。在这个过程中,您将学习如何编写自己的函数,并使用 awk 的多维数组。学完本文之后,您将掌握更多 awk 经验,可以让您创建功能更强大的脚本。
标记本文!
发布日期: 2001 年 4 月 01 日
级别: 初级
访问情况 : 5562 次浏览
评论: 0 (查看 | 添加评论 - 登录)
平均分 (8个评分)
为本文评分
格式化输出
虽然大多数情况下 awk 的 print 语句可以完成任务,但有时我们还需要更多。在那些情况下,awk 提供了两个我们熟知的老朋友 printf() 和 sprintf()。是的,如同其它许多 awk 部件一样,这些函数等同于相应的 C 语言函数。printf() 会将格式化字符串打印到 stdout,而 sprintf() 则返回可以赋值给变量的格式化字符串。如果不熟悉 printf() 和 sprintf(),介绍 C 语言的文章可以让您迅速了解这两个基本打印函数。在 Linux 系统上,可以输入 "man 3 printf" 来查看 printf() 帮助页面。
以下是一些 awk sprintf() 和 printf() 的样本代码。可以看到,它们几乎与 C 语言完全相同。
x=1 b="foo" printf("%s got a %d on the last test\n","Jim",83) myout=("%s-%d",b,x) print myout |
Jim got a 83 on the last test foo-1 |
字符串函数
awk 有许多字符串函数,这是件好事。在 awk 中,确实需要字符串函数,因为不能象在其它语言(如 C、C++ 和 Python)中那样将字符串看作是字符数组。例如,如果执行以下代码:
mystring="How are you doing today?" print mystring[3] |
awk: string.gawk:59: fatal: attempt to use scalar as array |
首先,有一个基本 length() 函数,它返回字符串的长度。以下是它的使用方法:
print length(mystring) |
24 |
print index(mystring,"you") |
9 |
print tolower(mystring) print toupper(mystring) print mystring |
how are you doing today? HOW ARE YOU DOING TODAY? How are you doing today? |
mysub=substr(mystring,startpos,maxlen) |
print substr(mystring,9,3) |
you |
现在,我们讨论一些更耐人寻味的函数,首先是 match()。match() 与 index() 非常相似,它与 index() 的区别在于它并不搜索子串,它搜索的是规则表达式。match() 函数将返回匹配的起始位置,如果没有找到匹配,则返回 0。此外,match() 还将设置两个变量,叫作 RSTART 和 RLENGTH。RSTART 包含返回值(第一个匹配的位置),RLENGTH 指定它占据的字符跨度(如果没有找到匹配,则返回 -1)。通过使用 RSTART、RLENGTH、substr() 和一个小循环,可以轻松地迭代字符串中的每个匹配。以下是一个 match() 调用示例:
print match(mystring,/you/), RSTART, RLENGTH |
9 9 3 |
字符串替换
现在,我们将研究两个字符串替换函数,sub() 和 gsub()。这些函数与目前已经讨论过的函数略有不同,因为它们 确实修改原始字符串 。以下是一个模板,显示了如何调用 sub():
sub(regexp,replstring,mystring) |
sub(/o/,"O",mystring) print mystring mystring="How are you doing today?" gsub(/o/,"O",mystring) print mystring |
HOw are you doing today? HOw are yOu dOing tOday? |
通过介绍函数 split(),我们来汇总一下已讨论过的函数。split() 的任务是“切开”字符串,并将各部分放到使用整数下标的数组中。以下是一个 split() 调用示例:
numelements=split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",mymonths,",") |
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 |
23 Aug 2000 - inco - Y Boss Man 2001.00 |
用于计算当前余额的算法不太难。awk 只需要依次读取每一行。如果列出了费用分类帐,但没有收入分类帐(为 "-"),那么这一项就是借方。如果列出了收入分类帐,但没有费用分类帐(为 "-"),那么这一项就是贷方。而且,如果同时列出了费用和收入分类帐,那么这个金额就是“分类帐转帐”;即,从费用分类帐减去美元金额,并将此金额添加到收入分类帐。此外,所有这些分类帐都是虚拟的,但对于跟踪收入和支出以及预算却非常有用。
回页首
代码
现在该研究代码了。我们将从第一行(BEGIN 块和函数定义)开始:
balance,第 1 部分
#!/usr/bin/env awk -f BEGIN { FS="\t+" months="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec" } function monthdigit(mymonth) { return (index(months,mymonth)+3)/4 } |
最后三行显示了如何定义自己的 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"]。
balance,第 2 部分
function doincome(mybalance) { mybalance[curmonth,$3] += amount mybalance[0,$3] += amount } function doexpense(mybalance) { mybalance[curmonth,$2] -= amount mybalance[0,$2] -= amount } function dotransfer(mybalance) { mybalance[0,$2] -= amount mybalance[curmonth,$2] -= amount mybalance[0,$3] += amount mybalance[curmonth,$3] += amount } |
如果研究这些函数,将发现在我的引用中传递了 mybalance 引用的数组。另外,我们还引用了几个全局变量:curmonth,它保存了当前记录所属的月份的数值,$2(费用分类帐),$3(收入分类帐)和金额($7,美元金额)。调用 doincome() 和其它函数时,已经为要处理的当前记录(行)正确设置了所有这些变量。
回页首
主块
以下是主代码块,它包含了分析每一行输入数据的代码。请记住,由于正确设置了 FS,可以用 $ 1 引用第一个字段,用 $2 引用第二个字段,依次类推。调用 doincome() 和其它函数时,这些函数可以从函数内部访问 curmonth、$2、$3 和金额的当前值。请先研究代码,在代码之后可以见到我的说明。
balance,第 3 部分
{ curmonth=monthdigit(substr($1,4,3)) amount=$7 #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) } } |
在接着的大约二十行中,我们分析字段 $2 和 $3,并适当记录交易。如果 $2=="-" 且 $3!="-",表示我们有收入,因此调用 doincome()。如果是相反的情况,则调用 doexpense();如果 $2 和 $3 都包含分类帐,则调用 dotransfer()。每次我们都将 "balance" 数组传递给这些函数,从而在这些函数中记录适当的数据。
您还会发现几行代码说“if ( $5 == "Y" ),那么将同一个交易记录到 balance2 中”。我们在这里究竟做了些什么?您将回忆起 $5 包含 "Y" 或 "N",并记录交易是否已经过帐到帐户。由于仅当过帐了交易时我们才将交易记录到 balance2,因此 balance2 包含了真实的帐户余额,而 "balance" 包含了所有交易,不管是否已经过帐。可以使用 balance2 来验证数据项(因为它应该与当前银行帐户余额匹配),可以使用 "balance" 来确保没有透支帐户(因为它会考虑您开出的尚未兑现的所有支票)。
回页首
生成报表
主块重复处理了每一行记录之后,现在我们有了关于比较全面的、按分类帐和按月份划分的借方和贷方记录。现在,在这种情况下最合适的做法是只须定义生成报表的 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 |
回页首
升级
我使用这个程序的更高级版本来管理我的个人和企业财务。我的版本(由于篇幅限制不能在此涵盖)会打印出收入和费用的月度明细分类帐,包括年度总合、净收入和其它许多内容。它甚至以 HTML 格式输出数据,因此我可以在 Web 浏览器中查看它。:) 如果您认为这个程序有用,我建议您将这些特性添加到这个脚本中。不必将它配置成要 记录 任何附加信息;所需的全部信息已经在 balance 和 balance2 里面了。只要升级 END 块就万事具备了!
我希望您喜欢本系列。有关 awk 的详细信息,请参考以下列出的参考资料。
参考资料
您可以参阅本文在 developerWorks 全球站点上的 英文原文.
请阅读 Daniel 在 developerWorks 上发表的 awk 系列中的前几篇文章:awk 实例, 第 1 部分和 第 2 部分。
如果想看好的老式书籍,O'Reilly 的 sed & awk, 2ndEdition是极佳选择。
请参考 comp.lang.awkFAQ 。它还包含许多附加 awk 链接。
Patrick Hartigan 的 awk tutorial 还包括了实用的 awk 脚本。
Thompson's TAWKCompiler 将 awk 脚本编译成快速二进制可执行文件。可用版本有 Windows 版、OS/2 版、DOS 版和 UNIX 版。
The GNUAwk User's Guide可用于在线参考。
关于作者
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 联系。
相关文章推荐
- zz 通用线程: awk 实例,第 1 部分
- zz 通用线程:Awk 实例,第 2部分
- 通用线程:Awk 实例,第 1部分
- 通用线程:awk 实例,第 1 部分:一种名称很奇特的优秀语言介绍
- 通用线程:Awk 实例,第 2部分
- 通用线程:Awk 实例,第 2部分
- 通用线程:Awk 实例 1
- 通用线程:Awk 实例 2
- 通用线程:Awk 实例 3
- sed与Awk教程入门与实例练习(ZZ)
- 通用线程 -- sed 实例
- 通用线程 -- sed 实例
- 通用线程 -- sed 实例 1
- 通用线程 -- sed 实例
- 通用线程 -- sed 实例 2
- 通用线程 -- sed 实例二
- IBM网站实用资料:AWK实例第1,2,3部分
- 通用线程 -- sed 实例 3
- 通用线程 -- sed 实例三
- java中线程死锁实例