- 論壇徽章:
- 0
|
求助=>誰能介紹一下awk語言及語法
***********************************************
我把文章拷過來了,來幫助我的人順便看看,感謝大家!
***********************************************
====== 在 學(xué) 習(xí) UNIX 的 道 路 上 有 南 非 蜘 蛛 陪 伴 所 以 不 寂 寞 ======
>;>;DouZhe.com>; SCO 返回精華版首頁
通用線程:Awk 實(shí)例(轉(zhuǎn)貼)
作者:donggua1 發(fā)表時(shí)間:2002/08/29 08:24am
通用線程:Awk 實(shí)例
Daniel Robbins (drobbins@gentoo.org)
第一部分
Awk 是一種非常好的語言,同時(shí)有一個(gè)非常奇怪的名稱。在本系列(共三篇文章)的第一篇文章中,Daniel Robbins 將使您迅速掌握 awk 編程技巧。隨著本系列的進(jìn)展,將討論更高級(jí)的主題,最后將演示一個(gè)真正的高級(jí) awk 演示程序。
捍衛(wèi) awk
在本系列文章中,我將使您成為精通 awk 的編碼人員。我承認(rèn),awk 并沒有一個(gè)非常好聽且又非常“時(shí)髦”的名字。awk 的 GNU 版本(叫作 gawk)聽起來非常怪異。那些不熟悉這種語言的人可能聽說過 "awk",并可能認(rèn)為它是一組落伍且過時(shí)的混亂代碼。它甚至?xí)棺畈⿲W(xué)的 UNIX 權(quán)威陷于錯(cuò)亂的邊緣(使他不斷地發(fā)出 "kill -9!" 命令,就象使用咖啡機(jī)一樣)。
的確,awk 沒有一個(gè)動(dòng)聽的名字。但它是一種很棒的語言。awk 適合于文本處理和報(bào)表生成,它還有許多精心設(shè)計(jì)的特性,允許進(jìn)行需要特殊技巧程序設(shè)計(jì)。與某些語言不同,awk 的語法較為常見。它借鑒了某些語言的一些精華部分,如 C 語言、python 和 bash(雖然在技術(shù)上,awk 比 python 和 bash 早創(chuàng)建)。awk 是那種一旦學(xué)會(huì)了就會(huì)成為您戰(zhàn)略編碼庫的主要部分的語言。
第一個(gè) awk
讓我們繼續(xù),開始使用 awk,以了解其工作原理。在命令行中輸入以下命令:
$ awk '{ print }' /etc/passwd
您將會(huì)見到 /etc/passwd 文件的內(nèi)容出現(xiàn)在眼前。現(xiàn)在,解釋 awk 做了些什么。調(diào)用 awk 時(shí),我們指定 /etc/passwd 作為輸入文件。執(zhí)行 awk 時(shí),它依次對(duì) /etc/passwd 中的每一行執(zhí)行 print 命令。所有輸出都發(fā)送到 stdout,所得到的結(jié)果與與執(zhí)行catting /etc/passwd完全相同。
現(xiàn)在,解釋 { print } 代碼塊。在 awk 中,花括號(hào)用于將幾塊代碼組合到一起,這一點(diǎn)類似于 C 語言。在代碼塊中只有一條 print 命令。在 awk 中,如果只出現(xiàn) print 命令,那么將打印當(dāng)前行的全部?jī)?nèi)容。
這里是另一個(gè) awk 示例,它的作用與上例完全相同:
$ awk '{ print $0 }' /etc/passwd
在 awk 中,$0 變量表示整個(gè)當(dāng)前行,所以 print 和 print $0 的作用完全一樣。
如果您愿意,可以創(chuàng)建一個(gè) awk 程序,讓它輸出與輸入數(shù)據(jù)完全無關(guān)的數(shù)據(jù)。以下是一個(gè)示例:
$ awk '{ print "" }' /etc/passwd
只要將 "" 字符串傳遞給 print 命令,它就會(huì)打印空白行。如果測(cè)試該腳本,將會(huì)發(fā)現(xiàn)對(duì)于 /etc/passwd 文件中的每一行,awk 都輸出一個(gè)空白行。再次說明, awk 對(duì)輸入文件中的每一行都執(zhí)行這個(gè)腳本。以下是另一個(gè)示例:
$ awk '{ print "hiya" }' /etc/passwd
運(yùn)行這個(gè)腳本將在您的屏幕上寫滿 hiya。
多個(gè)字段
awk 非常善于處理分成多個(gè)邏輯字段的文本,而且讓您可以毫不費(fèi)力地引用 awk 腳本中每個(gè)獨(dú)立的字段。以下腳本將打印出您的系統(tǒng)上所有用戶帳戶的列表:
$ awk -F":" '{ print $1 }' /etc/passwd
上例中,在調(diào)用 awk 時(shí),使用 -F 選項(xiàng)來指定 ":" 作為字段分隔符。awk 處理 print $1 命令時(shí),它會(huì)打印出在輸入文件中每一行中出現(xiàn)的第一個(gè)字段。以下是另一個(gè)示例
$ awk -F":" '{ print $1 $3 }' /etc/passwd
以下是該腳本輸出的摘錄
halt7operator11root0shutdown6sync5bin1....etc.
如您所見,awk 打印出 /etc/passwd 文件的第一和第三個(gè)字段,它們正好分別是用戶名和用戶標(biāo)識(shí)字段。現(xiàn)在,當(dāng)腳本運(yùn)行時(shí),它并不理想 -- 在兩個(gè)輸出字段之間沒有空格!如果習(xí)慣于使用 bash 或 python 進(jìn)行編程,那么您會(huì)指望 print $1 $3 命令在兩個(gè)字段之間插入空格。然而,當(dāng)兩個(gè)字符串在 awk 程序中彼此相鄰時(shí),awk 會(huì)連接它們但不在它們之間添加空格。以下命令會(huì)在這兩個(gè)字段中插入空格:
$ awk -F":" '{ print $1 " " $3 }' /etc/passwd
以這種方式調(diào)用 print 時(shí),它將連接 $1、" " 和 $3,創(chuàng)建可讀的輸出。當(dāng)然,如果需要的話,我們還可以插入一些文本標(biāo)簽:
$ awk -F":" '{ print "username: " $1 "\t\tuid:" $3" }' /etc/passwd
這將產(chǎn)生以下輸出:
username: halt uid:7username: operator uid:11username: root uid:0username: shutdown uid:6username: sync uid:5username: bin uid:1....etc.
外部腳本
將腳本作為命令行自變量傳遞給 awk 對(duì)于小的單行程序來說是非常簡(jiǎn)單的,而對(duì)于多行程序,它就比較復(fù)雜。您肯定想要在外部文件中撰寫腳本。然后可以向 awk 傳遞 -f 選項(xiàng),以向它提供此腳本文件:
$ awk -f myscript.awk myfile.in
將腳本放入文本文件還可以讓您使用附加 awk 功能。例如,這個(gè)多行腳本與前面的單行腳本的作用相同,它們都打印出 /etc/passwd 中每一行的第一個(gè)字段:
BEGIN { FS=":"}{ print $1 }
這兩個(gè)方法的差別在于如何設(shè)置字段分隔符。在這個(gè)腳本中,字段分隔符在代碼自身中指定(通過設(shè)置 FS 變量),而在前一個(gè)示例中,通過在命令行上向 awk 傳遞 -F":" 選項(xiàng)來設(shè)置 FS。通常,最好在腳本自身中設(shè)置字段分隔符,只是因?yàn)檫@表示您可以少輸入一個(gè)命令行自變量。我們將在本文的后面詳細(xì)討論 FS 變量。
BEGIN 和 END 塊
通常,對(duì)于每個(gè)輸入行,awk 都會(huì)執(zhí)行每個(gè)腳本代碼塊一次。然而,在許多編程情況中,可能需要在 awk 開始處理輸入文件中的文本之前執(zhí)行初始化代碼。對(duì)于這種情況,awk 允許您定義一個(gè) BEGIN 塊。我們?cè)谇耙粋(gè)示例中使用了 BEGIN 塊。因?yàn)?awk 在開始處理輸入文件之前會(huì)執(zhí)行 BEGIN 塊,因此它是初始化 FS(字段分隔符)變量、打印頁眉或初始化其它在程序中以后會(huì)引用的全局變量的極佳位置。
awk 還提供了另一個(gè)特殊塊,叫作 END 塊。awk 在處理了輸入文件中的所有行之后執(zhí)行這個(gè)塊。通常,END 塊用于執(zhí)行最終計(jì)算或打印應(yīng)該出現(xiàn)在輸出流結(jié)尾的摘要信息。
規(guī)則表達(dá)式和塊
awk 允許使用規(guī)則表達(dá)式,根據(jù)規(guī)則表達(dá)式是否匹配當(dāng)前行來選擇執(zhí)行獨(dú)立代碼塊。以下示例腳本只輸出包含字符序列 foo 的那些行:
/foo/ { print }
當(dāng)然,可以使用更復(fù)雜的規(guī)則表達(dá)式。以下腳本將只打印包含浮點(diǎn)數(shù)的行:
/[0-9]+\.[0-9]*/ { print }
表達(dá)式和塊
還有許多其它方法可以選擇執(zhí)行代碼塊。我們可以將任意一種布爾表達(dá)式放在一個(gè)代碼塊之前,以控制何時(shí)執(zhí)行某特定塊。僅當(dāng)對(duì)前面的布爾表達(dá)式求值為真時(shí),awk 才執(zhí)行代碼塊。以下示例腳本輸出將輸出其第一個(gè)字段等于 fred 的所有行中的第三個(gè)字段。如果當(dāng)前行的第一個(gè)字段不等于 fred,awk 將繼續(xù)處理文件而不對(duì)當(dāng)前行執(zhí)行 print 語句:
$1 == "fred" { print $3 }
awk 提供了完整的比較運(yùn)算符集合,包括 "=="、"<"、">;"、"<="、">;=" 和 "!="。另外,awk 還提供了 "~" 和 "!~" 運(yùn)算符,它們分別表示“匹配”和“不匹配”。它們的用法是在運(yùn)算符左邊指定變量,在右邊指定規(guī)則表達(dá)式。如果某一行的第五個(gè)字段包含字符序列 root,那么以下示例將只打印這一行中的第三個(gè)字段:
$5 ~ /root/ { print $3 }
條件語句
awk 還提供了非常好的類似于 C 語言的 if 語句。如果您愿意,可以使用 if 語句重寫前一個(gè)腳本:
{ if ( $5 ~ /root/ ) { print $3 }}
這兩個(gè)腳本的功能完全一樣。第一個(gè)示例中,布爾表達(dá)式放在代碼塊外面。而在第二個(gè)示例中,將對(duì)每一個(gè)輸入行執(zhí)行代碼塊,而且我們使用 if 語句來選擇執(zhí)行 print 命令。這兩個(gè)方法都可以使用,可以選擇最適合腳本其它部分的一種方法。
以下是更復(fù)雜的 awk if 語句示例?梢钥吹,盡管使用了復(fù)雜、嵌套的條件語句,if 語句看上去仍與相應(yīng)的 C 語言 if 語句一樣:
{ if ( $1 == "foo" ) { if ( $2 == "foo" ) { print "uno" } else { print "one" } } else if ($1 == "bar" ) { print "two" } else { print "three" }}
使用 if 語句還可以將代碼:
! /matchme/ { print $1 $3 $4 }
轉(zhuǎn)換成:
{ if ( $0 !~ /matchme/ ) { print $1 $3 $4 }}
這兩個(gè)腳本都只輸出不包含 matchme 字符序列的那些行。此外,還可以選擇最適合您的代碼的方法。它們的功能完全相同。
awk 還允許使用布爾運(yùn)算符 "||"(邏輯與)和 "&&"(邏輯或),以便創(chuàng)建更復(fù)雜的布爾表達(dá)式:
( $1 == "foo" ) && ( $2 == "bar" ) { print }
這個(gè)示例只打印第一個(gè)字段等于 foo 且第二個(gè)字段等于 bar 的那些行。
數(shù)值變量!
至今,我們不是打印字符串、整行就是特定字段。然而,awk 還允許我們執(zhí)行整數(shù)和浮點(diǎn)運(yùn)算。通過使用數(shù)學(xué)表達(dá)式,可以很方便地編寫計(jì)算文件中空白行數(shù)量的腳本。以下就是這樣一個(gè)腳本:
BEGIN { x=0 }/^$/ { x=x+1 }END { print "I found " x " blank lines. " }
在 BEGIN 塊中,將整數(shù)變量 x 初始化成零。然后,awk 每次遇到空白行時(shí),awk 將執(zhí)行 x=x+1 語句,遞增 x。處理完所有行之后,執(zhí)行 END 塊,awk 將打印出最終摘要,指出它找到的空白行數(shù)量。
字符串化變量
awk 的優(yōu)點(diǎn)之一就是“簡(jiǎn)單和字符串化”。我認(rèn)為 awk 變量“字符串化”是因?yàn)樗?awk 變量在內(nèi)部都是按字符串形式存儲(chǔ)的。同時(shí),awk 變量是“簡(jiǎn)單的”,因?yàn)榭梢詫?duì)它執(zhí)行數(shù)學(xué)操作,且只要變量包含有效數(shù)字字符串,awk 會(huì)自動(dòng)處理字符串到數(shù)字的轉(zhuǎn)換步驟。要理解我的觀點(diǎn),請(qǐng)研究以下這個(gè)示例:
x="1.01"# We just set x to contain the *string* "1.01"x=x+1# We just added one to a *string* print x# Incidentally, these are comments 
awk 將輸出:
2.01
有趣吧!雖然將字符串值 1.01 賦值給變量 x,我們?nèi)匀豢梢詫?duì)它加一。但在 bash 和 python 中卻不能這樣做。首先,bash 不支持浮點(diǎn)運(yùn)算。而且,如果 bash 有“字符串化”變量,它們并不“簡(jiǎn)單”;要執(zhí)行任何數(shù)學(xué)操作,bash 要求我們將數(shù)字放到丑陋的 $( ) ) 結(jié)構(gòu)中。如果使用 python,則必須在對(duì) 1.01 字符串執(zhí)行任何數(shù)學(xué)運(yùn)算之前,將它轉(zhuǎn)換成浮點(diǎn)值。雖然這并不困難,但它仍是附加的步驟。如果使用 awk,它是全自動(dòng)的,而那會(huì)使我們的代碼又好又整潔。如果想要對(duì)每個(gè)輸入行的第一個(gè)字段乘方并加一,可以使用以下腳本:
{ print ($1^2)+1 }
如果做一個(gè)小實(shí)驗(yàn),就可以發(fā)現(xiàn)如果某個(gè)特定變量不包含有效數(shù)字,awk 在對(duì)數(shù)學(xué)表達(dá)式求值時(shí)會(huì)將該變量當(dāng)作數(shù)字零處理。
眾多運(yùn)算符
awk 的另一個(gè)優(yōu)點(diǎn)是它有完整的數(shù)學(xué)運(yùn)算符集合。除了標(biāo)準(zhǔn)的加、減、乘、除,awk 還允許使用前面演示過的指數(shù)運(yùn)算符 "^"、模(余數(shù))運(yùn)算符 "%" 和其它許多從 C 語言中借入的易于使用的賦值操作符。
這些運(yùn)算符包括前后加減(i++、--foo)、加/減/乘/除賦值運(yùn)算符( a+=3、b*=2、c/=2.2、d-=6.2)。不僅如此 -- 我們還有易于使用的模/指數(shù)賦值運(yùn)算符(a^=2、b%=4)。
字段分隔符
awk 有它自己的特殊變量集合。其中一些允許調(diào)整 awk 的運(yùn)行方式,而其它變量可以被讀取以收集關(guān)于輸入的有用信息。我們已經(jīng)接觸過這些特殊變量中的一個(gè),F(xiàn)S。前面已經(jīng)提到過,這個(gè)變量讓您可以設(shè)置 awk 要查找的字段之間的字符序列。我們使用 /etc/passwd 作為輸入時(shí),將 FS 設(shè)置成 ":"。當(dāng)這樣做有問題時(shí),我們還可以更靈活地使用 FS。
FS 值并沒有被限制為單一字符;可以通過指定任意長(zhǎng)度的字符模式,將它設(shè)置成規(guī)則表達(dá)式。如果正在處理由一個(gè)或多個(gè) tab 分隔的字段,您可能希望按以下方式設(shè)置 FS:
FS="\t+"
以上示例中,我們使用特殊 "+" 規(guī)則表達(dá)式字符,它表示“一個(gè)或多個(gè)前一字符”。
如果字段由空格分隔(一個(gè)或多個(gè)空格或 tab),您可能想要將 FS 設(shè)置成以下規(guī)則表達(dá)式:
FS="[[]+]"
這個(gè)賦值表達(dá)式也有問題,它并非必要。為什么?因?yàn)槿笔∏闆r下,F(xiàn)S 設(shè)置成單一空格字符,awk 將這解釋成表示“一個(gè)或多個(gè)空格或 tab”。在這個(gè)特殊示例中,缺省 FS 設(shè)置恰恰是您最想要的!
復(fù)雜的規(guī)則表達(dá)式也不成問題。即使您的記錄由單詞 "foo" 分隔,后面跟著三個(gè)數(shù)字,以下規(guī)則表達(dá)式仍允許對(duì)數(shù)據(jù)進(jìn)行正確的分析:
FS="foo[0-9][0-9][0-9]"
字段數(shù)量
接著我們要討論的兩個(gè)變量通常并不是需要賦值的,而是用來讀取以獲取關(guān)于輸入的有用信息。第一個(gè)是 NF 變量,也叫做“字段數(shù)量”變量。awk 會(huì)自動(dòng)將該變量設(shè)置成當(dāng)前記錄中的字段數(shù)量?梢允褂 NF 變量來只顯示某些輸入行:
NF == 3 { print "this particular record has three fields: " $0 }
當(dāng)然,也可以在條件語句中使用 NF 變量,如下:
{ if ( NF >; 2 ) { print $1 " " $2 ":" $3 }}
記錄號(hào)
記錄號(hào) (NR) 是另一個(gè)方便的變量。它始終包含當(dāng)前記錄的編號(hào)(awk 將第一個(gè)記錄算作記錄號(hào) 1)。迄今為止,我們已經(jīng)處理了每一行包含一個(gè)記錄的輸入文件。對(duì)于這些情況,NR 還會(huì)告訴您當(dāng)前行號(hào)。然而,當(dāng)我們?cè)诒鞠盗幸院蟛糠种虚_始處理多行記錄時(shí),就不會(huì)再有這種情況,所以要注意!可以象使用 NF 變量一樣使用 NR 來只打印某些輸入行:
(NR < 10 ) || (NR >; 100) { print "We are on record number 1-9 or 101+" }
另一個(gè)示例:
{ #skip header if ( NR >; 10 ) { print "ok, now for the real information!" }}
awk 提供了適合各種用途的附加變量。我們將在以后的文章中討論這些變量。
現(xiàn)在已經(jīng)到了初次探索 awk 的尾聲。隨著本系列的開展,我將演示更高級(jí)的 awk 功能,我們將用一個(gè)真實(shí)的 awk 應(yīng)用程序作為本系列的結(jié)尾。同時(shí),如果急于學(xué)習(xí)更多知識(shí),請(qǐng)參考以下列出的參考資料。
第二部分
在這篇 awk 簡(jiǎn)介的續(xù)集中,Daniel Robbins 繼續(xù)探索 awk(一種很棒但有怪異名稱的語言)。Daniel 將演示如何處理多行記錄、使用循環(huán)結(jié)構(gòu),以及創(chuàng)建并使用 awk 數(shù)組。閱讀完本文后,您將精通許多 awk 的功能,而且可以編寫您自己的功能強(qiáng)大的 awk 腳本。
多行記錄
awk 是一種用于讀取和處理結(jié)構(gòu)化數(shù)據(jù)(如系統(tǒng)的 /etc/passwd 文件)的極佳工具。/etc/passwd 是 UNIX 用戶數(shù)據(jù)庫,并且是用冒號(hào)定界的文本文件,它包含許多重要信息,包括所有現(xiàn)有用戶帳戶和用戶標(biāo)識(shí),以及其它信息。在我的前一篇文章中,我演示了 awk 如何輕松地分析這個(gè)文件。我們只須將 FS(字段分隔符)變量設(shè)置成 ":"。
正確設(shè)置了 FS 變量之后,就可以將 awk 配置成分析幾乎任何類型的結(jié)構(gòu)化數(shù)據(jù),只要這些數(shù)據(jù)是每行一個(gè)記錄。然而,如果要分析占據(jù)多行的記錄,僅僅依靠設(shè)置 FS 是不夠的。在這些情況下,我們還需要修改 RS 記錄分隔符變量。RS 變量告訴 awk 當(dāng)前記錄什么時(shí)候結(jié)束,新記錄什么時(shí)候開始。
譬如,讓我們討論一下如何完成處理“聯(lián)邦證人保護(hù)計(jì)劃”所涉及人員的地址列表的任務(wù):
Jimmy the Weasel100 Pleasant DriveSan Francisco, CA 12345Big Tony200 Incognito Ave.Suburbia, WA 67890
理論上,我們希望 awk 將每 3 行看作是一個(gè)獨(dú)立的記錄,而不是三個(gè)獨(dú)立的記錄。如果 awk 將地址的第一行看作是第一個(gè)字段 ($1),街道地址看作是第二個(gè)字段 ($2),城市、州和郵政編碼看作是第三個(gè)字段 $3,那么這個(gè)代碼就會(huì)變得很簡(jiǎn)單。以下就是我們想要得到的代碼:
BEGIN { FS="\n" RS=""}
在上面這段代碼中,將 FS 設(shè)置成 "\n" 告訴 awk 每個(gè)字段都占據(jù)一行。通過將 RS 設(shè)置成 "",還會(huì)告訴 awk 每個(gè)地址記錄都由空白行分隔。一旦 awk 知道是如何格式化輸入的,它就可以為我們執(zhí)行所有分析工作,腳本的其余部分很簡(jiǎn)單。讓我們研究一個(gè)完整的腳本,它將分析這個(gè)地址列表,并將每個(gè)記錄打印在一行上,用逗號(hào)分隔每個(gè)字段。
address.awk
BEGIN { FS="\n" RS=""}{ print $1 ", " $2 ", " $3}
如果這個(gè)腳本保存為 address.awk,地址數(shù)據(jù)存儲(chǔ)在文件 address.txt 中,可以通過輸入 "awk -f address.awk address.txt" 來執(zhí)行這個(gè)腳本。此代碼將產(chǎn)生以下輸出:
Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345Big Tony, 200 Incognito Ave., Suburbia, WA 67890
OFS 和 ORS
在 address.awk 的 print 語句中,可以看到 awk 會(huì)連接(合并)一行中彼此相鄰的字符串。我們使用此功能在同一行上的三個(gè)字段之間插入一個(gè)逗號(hào)和空格 (", " 。這個(gè)方法雖然有用,但比較難看。與其在字段間插入 ", " 字符串,倒不如讓通過設(shè)置一個(gè)特殊 awk 變量 OFS,讓 awk 完成這件事。請(qǐng)參考下面這個(gè)代碼片斷。
print "Hello", "there", "Jim!"
這行代碼中的逗號(hào)并不是實(shí)際文字字符串的一部分。事實(shí)上,它們告訴 awk "Hello"、"there" 和 "Jim!" 是單獨(dú)的字段,并且應(yīng)該在每個(gè)字符串之間打印 OFS 變量。缺省情況下,awk 產(chǎn)生以下輸出:
Hello there Jim!
這是缺省情況下的輸出結(jié)果,OFS 被設(shè)置成 " ",單個(gè)空格。不過,我們可以方便地重新定義 OFS,這樣 awk 將插入我們中意的字段分隔符。以下是原始 address.awk 程序的修訂版,它使用 OFS 來輸出那些中間的 ", " 字符串:
address.awk 的修訂版
BEGIN { FS="\n" RS="" OFS=", "}{ print $1, $2, $3}
awk 還有一個(gè)特殊變量 ORS,全稱是“輸出記錄分隔符”。通過設(shè)置缺省為換行 ("\n" 的 OFS,我們可以控制在 print 語句結(jié)尾自動(dòng)打印的字符。缺省 ORS 值會(huì)使 awk 在新行中輸出每個(gè)新的 print 語句。如果想使輸出的間隔翻倍,可以將 ORS 設(shè)置成 "\n\n";蛘,如果想要用單個(gè)空格分隔記錄(而不換行),將 ORS 設(shè)置成 ""。
將多行轉(zhuǎn)換成用 tab 分隔的格式
假設(shè)我們編寫了一個(gè)腳本,它將地址列表轉(zhuǎn)換成每個(gè)記錄一行,且用 tab 定界的格式,以便導(dǎo)入電子表格。使用稍加修改的 address.awk 之后,就可以清楚地看到這個(gè)程序只適合于三行的地址。如果 awk 遇到以下地址,將丟掉第四行,并且不打印該行:
Cousin VinnieVinnie's Auto Shop300 City AlleySosueme, OR 76543
要處理這種情況,代碼最好考慮每個(gè)字段的記錄數(shù)量,并依次打印每個(gè)記錄,F(xiàn)在,代碼只打印地址的前三個(gè)字段。以下就是我們想要的一些代碼:
適合具有任意多字段的地址的 address.awk 版本
BEGIN { FS="\n" RS="" ORS="" } { x=1 while ( x<NF ) { print $x "\t" x++ } print $NF "\n" }
首先,將字段分隔符 FS 設(shè)置成 "\n",將記錄分隔符 RS 設(shè)置成 "",這樣 awk 可以象以前一樣正確分析多行地址。然后,將輸出記錄分隔符 ORS 設(shè)置成 "",它將使 print 語句在每個(gè)調(diào)用結(jié)尾不輸出新行。這意味著如果希望任何文本從新的一行開始,那么需要明確寫入 print "\n"。
在主代碼塊中,創(chuàng)建了一個(gè)變量 x 來存儲(chǔ)正在處理的當(dāng)前字段的編號(hào)。起初,它被設(shè)置成 1。然后,我們使用 while 循環(huán)(一種 awk 循環(huán)結(jié)構(gòu),等同于 C 語言中的 while 循環(huán)),對(duì)于所有記錄(最后一個(gè)記錄除外)重復(fù)打印記錄和 tab 字符。最后,打印最后一個(gè)記錄和換行;此外,由于將 ORS 設(shè)置成 "",print 將不輸出換行。程序輸出如下,這正是我們所期望的:
我們想要的輸出。不算漂亮,但用 tab 定界,以便于導(dǎo)入電子表格
Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345 Big Tony 200 Incognito Ave. Suburbia, WA 67890Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543
循環(huán)結(jié)構(gòu)
我們已經(jīng)看到了 awk 的 while 循環(huán)結(jié)構(gòu),它等同于相應(yīng)的 C 語言 while 循環(huán)。awk 還有 "do...while" 循環(huán),它在代碼塊結(jié)尾處對(duì)條件求值,而不象標(biāo)準(zhǔn) while 循環(huán)那樣在開始處求值。它類似于其它語言中的 "repeat...until" 循環(huán)。以下是一個(gè)示例:
do...while 示例
{ count=1 do { print "I get printed at least once no matter what" } while ( count != 1 )}
與一般的 while 循環(huán)不同,由于在代碼塊之后對(duì)條件求值,"do...while" 循環(huán)永遠(yuǎn)都至少執(zhí)行一次。換句話說,當(dāng)?shù)谝淮斡龅狡胀?while 循環(huán)時(shí),如果條件為假,將永遠(yuǎn)不執(zhí)行該循環(huán)。
for 循環(huán)
awk 允許創(chuàng)建 for 循環(huán),它就象 while 循環(huán),也等同于 C 語言的 for 循環(huán):
for ( initial assignment; comparison; increment ) { code block}
以下是一個(gè)簡(jiǎn)短示例:
for ( x = 1; x <= 4; x++ ) { print "iteration",x}
此段代碼將打印:
iteration 1iteration 2iteration 3iteration 4
break 和 continue
此外,如同 C 語言一樣,awk 提供了 break 和 continue 語句。使用這些語句可以更好地控制 awk 的循環(huán)結(jié)構(gòu)。以下是迫切需要 break 語句的代碼片斷:
while 死循環(huán)
while (1) { print "forever and ever..."}
因?yàn)?1 永遠(yuǎn)代表是真,這個(gè) while 循環(huán)將永遠(yuǎn)運(yùn)行下去。以下是一個(gè)只執(zhí)行十次的循環(huán):
break 語句示例
x=1while(1) { print "iteration",x if ( x == 10 ) { break } x++}
這里,break 語句用于“逃出”最深層的循環(huán)。"break" 使循環(huán)立即終止,并繼續(xù)執(zhí)行循環(huán)代碼塊后面的語句。
continue 語句補(bǔ)充了 break,其作用如下:
x=1while (1) { if ( x == 4 ) { x++ continue } print "iteration",x if ( x >; 20 ) { break } x++}
這段代碼打印 "iteration 1" 到 "iteration 21","iteration 4" 除外。如果迭代等于 4,則增加 x 并調(diào)用 continue 語句,該語句立即使 awk 開始執(zhí)行下一個(gè)循環(huán)迭代,而不執(zhí)行代碼塊的其余部分。如同 break 一樣,continue 語句適合各種 awk 迭代循環(huán)。在 for 循環(huán)主體中使用時(shí),continue 將使循環(huán)控制變量自動(dòng)增加。以下是一個(gè)等價(jià)循環(huán):
for ( x=1; x<=21; x++ ) { if ( x == 4 ) { continue } print "iteration",x}
在 while 循環(huán)中時(shí),在調(diào)用 continue 之前沒有必要增加 x,因?yàn)?for 循環(huán)會(huì)自動(dòng)增加 x。
數(shù)組
如果您知道 awk 可以使用數(shù)組,您一定會(huì)感到高興。然而,在 awk 中,數(shù)組下標(biāo)通常從 1 開始,而不是 0:
myarray[1]="jim"myarray[2]=456
awk 遇到第一個(gè)賦值語句時(shí),它將創(chuàng)建 myarray,并將元素 myarray[1] 設(shè)置成 "jim"。執(zhí)行了第二個(gè)賦值語句后,數(shù)組就有兩個(gè)元素了。
數(shù)組迭代
定義之后,awk 有一個(gè)便利的機(jī)制來迭代數(shù)組元素,如下所示:
for ( x in myarray ) { print myarray[x]}
這段代碼將打印數(shù)組 myarray 中的每一個(gè)元素。當(dāng)對(duì)于 for 使用這種特殊的 "in" 形式時(shí),awk 將 myarray 的每個(gè)現(xiàn)有下標(biāo)依次賦值給 x(循環(huán)控制變量),每次賦值以后都循環(huán)一次循環(huán)代碼。雖然這是一個(gè)非常方便的 awk 功能,但它有一個(gè)缺點(diǎn) -- 當(dāng) awk 在數(shù)組下標(biāo)之間輪轉(zhuǎn)時(shí),它不會(huì)依照任何特定的順序。那就意味著我們不能知道以上代碼的輸出是
jim456
還是
456jim
套用 Forrest Gump 的話來說,迭代數(shù)組內(nèi)容就像一盒巧克力 -- 您永遠(yuǎn)不知道將會(huì)得到什么。因此有必要使 awk 數(shù)組“字符串化”,我們現(xiàn)在就來研究這個(gè)問題。
數(shù)組下標(biāo)字符串化
在我的前一篇文章中,我演示了 awk 實(shí)際上以字符串格式來存儲(chǔ)數(shù)字值。雖然 awk 要執(zhí)行必要的轉(zhuǎn)換來完成這項(xiàng)工作,但它卻可以使用某些看起來很奇怪的代碼:
a="1"b="2"c=a+b+3
執(zhí)行了這段代碼后,c 等于 6。由于 awk 是“字符串化”的,添加字符串 "1" 和 "2" 在功能上并不比添加數(shù)字 1 和 2 難。這兩種情況下,awk 都可以成功執(zhí)行運(yùn)算。awk 的“字符串化”性質(zhì)非?蓯 -- 您可能想要知道如果使用數(shù)組的字符串下標(biāo)會(huì)發(fā)生什么情況。例如,使用以下代碼:
myarr["1"]="Mr. Whipple"print myarr["1"]
可以預(yù)料,這段代碼將打印 "Mr. Whipple"。但如果去掉第二個(gè) "1" 下標(biāo)中的引號(hào),情況又會(huì)怎樣呢?
myarr["1"]="Mr. Whipple"print myarr[1]
猜想這個(gè)代碼片斷的結(jié)果比較難。awk 將 myarr["1"] 和 myarr[1] 看作數(shù)組的兩個(gè)獨(dú)立元素,還是它們是指同一個(gè)元素?答案是它們指的是同一個(gè)元素,awk 將打印 "Mr. Whipple",如同第一個(gè)代碼片斷一樣。雖然看上去可能有點(diǎn)怪,但 awk 在幕后卻一直使用數(shù)組的字符串下標(biāo)!
了解了這個(gè)奇怪的真相之后,我們中的一些人可能想要執(zhí)行類似于以下的古怪代碼:
myarr["name"]="Mr. Whipple"print myarr["name"]
這段代碼不僅不會(huì)產(chǎn)生錯(cuò)誤,而且它的功能與前面的示例完全相同,也將打印 "Mr. Whipple"!可以看到,awk 并沒有限制我們使用純整數(shù)下標(biāo);如果我們?cè)敢,可以使用字符串下?biāo),而且不會(huì)產(chǎn)生任何問題。只要我們使用非整數(shù)數(shù)組下標(biāo),如 myarr["name"],那么我們就在使用關(guān)聯(lián)數(shù)組。從技術(shù)上講,如果我們使用字符串下標(biāo),awk 的后臺(tái)操作并沒有什么不同(因?yàn)榧幢闶褂谩罢麛?shù)”下標(biāo),awk 還是會(huì)將它看作是字符串)。但是,應(yīng)該將它們稱作關(guān)聯(lián)數(shù)組 -- 它聽起來很酷,而且會(huì)給您的上司留下印象。字符串化下標(biāo)是我們的小秘密。
數(shù)組工具
談到數(shù)組時(shí),awk 給予我們?cè)S多靈活性?梢允褂米址聵(biāo),而且不需要連續(xù)的數(shù)字序列下標(biāo)(例如,可以定義 myarr[1] 和 myarr[1000],但不定義其它所有元素)。雖然這些都很有用,但在某些情況下,會(huì)產(chǎn)生混淆。幸好,awk 提供了一些實(shí)用功能有助于使數(shù)組變得更易于管理。
首先,可以刪除數(shù)組元素。如果想要?jiǎng)h除數(shù)組 fooarray 的元素 1,輸入:
delete fooarray[1]
而且,如果想要查看是否存在某個(gè)特定數(shù)組元素,可以使用特殊的 "in" 布爾運(yùn)算符,如下所示:
if ( 1 in fooarray ) { print "Ayep! It's there."} else { print "Nope! Can't find it."}
下一篇
本文中,我們已經(jīng)討論了許多基礎(chǔ)知識(shí)。下一篇中,我將演示如何使用 awk 的數(shù)學(xué)運(yùn)算和字符串函數(shù),以及如何創(chuàng)建您自己的函數(shù),使您完全掌握 awk 知識(shí)。我還將指導(dǎo)您創(chuàng)建支票簿結(jié)算程序。那時(shí),我會(huì)鼓勵(lì)您編寫自己的 awk 程序。請(qǐng)查閱以下參考資料。
在 awk 系列的這篇總結(jié)中,Daniel 向您介紹 awk 重要的字符串函數(shù),以及演示了如何從頭開始編寫完整的支票簿結(jié)算程序。在這個(gè)過程中,您將學(xué)習(xí)如何編寫自己的函數(shù),并使用 awk 的多維數(shù)組。學(xué)完本文之后,您將掌握更多 awk 經(jīng)驗(yàn),可以讓您創(chuàng)建功能更強(qiáng)大的腳本。
第三部分
格式化輸出
雖然大多數(shù)情況下 awk 的 print 語句可以完成任務(wù),但有時(shí)我們還需要更多。在那些情況下,awk 提供了兩個(gè)我們熟知的老朋友 printf() 和 sprintf()。是的,如同其它許多 awk 部件一樣,這些函數(shù)等同于相應(yīng)的 C 語言函數(shù)。printf() 會(huì)將格式化字符串打印到 stdout,而 sprintf() 則返回可以賦值給變量的格式化字符串。如果不熟悉 printf() 和 sprintf(),介紹 C 語言的文章可以讓您迅速了解這兩個(gè)基本打印函數(shù)。在 Linux 系統(tǒng)上,可以輸入 "man 3 printf" 來查看 printf() 幫助頁面。
以下是一些 awk sprintf() 和 printf() 的樣本代碼?梢钥吹,它們幾乎與 C 語言完全相同。
x=1b="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 testfoo-1
字符串函數(shù)
awk 有許多字符串函數(shù),這是件好事。在 awk 中,確實(shí)需要字符串函數(shù),因?yàn)椴荒芟笤谄渌Z言(如 C、C++ 和 Python)中那樣將字符串看作是字符數(shù)組。例如,如果執(zhí)行以下代碼:
mystring="How are you doing today?"print mystring[3]
將會(huì)接收到一個(gè)錯(cuò)誤,如下所示:
awk: string.gawk:59: fatal: attempt to use scalar as array
噢,好吧。雖然不象 Python 的序列類型那樣方便,但 awk 的字符串函數(shù)還是可以完成任務(wù)。讓我們來看一下。
首先,有一個(gè)基本 length() 函數(shù),它返回字符串的長(zhǎng)度。以下是它的使用方法:
print length(mystring)
此代碼將打印值:
24
好,繼續(xù)。下一個(gè)字符串函數(shù)叫作 index,它將返回子字符串在另一個(gè)字符串中出現(xiàn)的位置,如果沒有找到該字符串則返回 0。使用 mystring,可以按以下方法調(diào)用它:
print index(mystring,"you"
awk 會(huì)打。
9
讓我們繼續(xù)討論另外兩個(gè)簡(jiǎn)單的函數(shù),tolower() 和 toupper()。與您猜想的一樣,這兩個(gè)函數(shù)將返回字符串并且將所有字符分別轉(zhuǎn)換成小寫或大寫。請(qǐng)注意,tolower() 和 toupper() 返回新的字符串,不會(huì)修改原來的字符串。這段代碼:
print tolower(mystring)print toupper(mystring)print mystring
……將產(chǎn)生以下輸出:
how are you doing today?HOW ARE YOU DOING TODAY?How are you doing today?
到現(xiàn)在為止一切不錯(cuò),但我們究竟如何從字符串中選擇子串,甚至單個(gè)字符?那就是使用 substr() 的原因。以下是 substr() 的調(diào)用方法:
mysub=substr(mystring,startpos,maxlen)
mystring 應(yīng)該是要從中抽取子串的字符串變量或文字字符串。startpos 應(yīng)該設(shè)置成起始字符位置,maxlen 應(yīng)該包含要抽取的字符串的最大長(zhǎng)度。請(qǐng)注意,我說的是最大長(zhǎng)度;如果 length(mystring) 比 startpos+maxlen 短,那么得到的結(jié)果就會(huì)被截?cái)唷ubstr() 不會(huì)修改原始字符串,而是返回子串。以下是一個(gè)示例:
print substr(mystring,9,3)
awk 將打。
you
如果您通常用于編程的語言使用數(shù)組下標(biāo)訪問部分字符串(以及不使用這種語言的人),請(qǐng)記住 substr() 是 awk 代替方法。需要使用它來抽取單個(gè)字符和子串;因?yàn)?awk 是基于字符串的語言,所以會(huì)經(jīng)常用到它。
現(xiàn)在,我們討論一些更耐人尋味的函數(shù),首先是 match()。match() 與 index() 非常相似,它與 index() 的區(qū)別在于它并不搜索子串,它搜索的是規(guī)則表達(dá)式。match() 函數(shù)將返回匹配的起始位置,如果沒有找到匹配,則返回 0。此外,match() 還將設(shè)置兩個(gè)變量,叫作 RSTART 和 RLENGTH。RSTART 包含返回值(第一個(gè)匹配的位置),RLENGTH 指定它占據(jù)的字符跨度(如果沒有找到匹配,則返回 -1)。通過使用 RSTART、RLENGTH、substr() 和一個(gè)小循環(huán),可以輕松地迭代字符串中的每個(gè)匹配。以下是一個(gè) match() 調(diào)用示例:
print match(mystring,/you/), RSTART, RLENGTH
awk 將打。
9 9 3
字符串替換
現(xiàn)在,我們將研究?jī)蓚(gè)字符串替換函數(shù),sub() 和 gsub()。這些函數(shù)與目前已經(jīng)討論過的函數(shù)略有不同,因?yàn)樗鼈兇_實(shí)修改原始字符串。以下是一個(gè)模板,顯示了如何調(diào)用 sub():
sub(regexp,replstring,mystring)
調(diào)用 sub() 時(shí),它將在 mystring 中匹配 regexp 的第一個(gè)字符序列,并且用 replstring 替換該序列。sub() 和 gsub() 用相同的自變量;唯一的區(qū)別是 sub() 將替換第一個(gè) regexp 匹配(如果有的話),gsub() 將執(zhí)行全局替換,換出字符串中的所有匹配。以下是一個(gè) sub() 和 gsub() 調(diào)用示例:
sub(/o/,"O",mystring)print mystringmystring="How are you doing today?"gsub(/o/,"O",mystring)print mystring
必須將 mystring 復(fù)位成其初始值,因?yàn)榈谝粋(gè) sub() 調(diào)用直接修改了 mystring。在執(zhí)行時(shí),此代碼將使 awk 輸出:
HOw are you doing today?HOw are yOu dOing tOday?
當(dāng)然,也可以是更復(fù)雜的規(guī)則表達(dá)式。我把測(cè)試一些復(fù)雜規(guī)則表達(dá)式的任務(wù)留給您來完成。
通過介紹函數(shù) split(),我們來匯總一下已討論過的函數(shù)。split() 的任務(wù)是“切開”字符串,并將各部分放到使用整數(shù)下標(biāo)的數(shù)組中。以下是一個(gè) split() 調(diào)用示例:
numelements=split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",mymonths,","
調(diào)用 split() 時(shí),第一個(gè)自變量包含要切開文字字符串或字符串變量。在第二個(gè)自變量中,應(yīng)該指定 split() 將填入片段部分的數(shù)組名稱。在第三個(gè)元素中,指定用于切開字符串的分隔符。split() 返回時(shí),它將返回分割的字符串元素的數(shù)量。split() 將每一個(gè)片段賦值給下標(biāo)從 1 開始的數(shù)組,因此以下代碼:
print mymonths[1],mymonths[numelements]
……將打印:
Jan Dec
特殊字符串形式
簡(jiǎn)短注釋 -- 調(diào)用 length()、sub() 或 gsub() 時(shí),可以去掉最后一個(gè)自變量,這樣 awk 將對(duì) $0(整個(gè)當(dāng)前行)應(yīng)用函數(shù)調(diào)用。要打印文件中每一行的長(zhǎng)度,使用以下 awk 腳本:
{ print length() }
財(cái)務(wù)上的趣事
幾星期前,我決定用 awk 編寫自己的支票簿結(jié)算程序。我決定使用簡(jiǎn)單的 tab 定界文本文件,以便于輸入最近的存款和提款記錄。其思路是將這個(gè)數(shù)據(jù)交給 awk 腳本,該腳本會(huì)自動(dòng)合計(jì)所有金額,并告訴我余額。以下是我決定如何將所有交易記錄到 "ASCII checkbook" 中:
23 Aug 2000 food - - Y Jimmy's Buffet 30.25
此文件中的每個(gè)字段都由一個(gè)或多個(gè) tab 分隔。在日期(字段 1,$1)之后,有兩個(gè)字段叫做“費(fèi)用分類帳”和“收入分類帳”。以上面這行為例,輸入費(fèi)用時(shí),我在費(fèi)用字段中放入四個(gè)字母的別名,在收入字段中放入 "-"(空白項(xiàng))。這表示這一特定項(xiàng)是“食品費(fèi)用”。 以下是存款的示例:
23 Aug 2000 - inco - Y Boss Man 2001.00
在這個(gè)實(shí)例中,我在費(fèi)用分類帳中放入 "-"(空白),在收入分類帳中放入 "inco"。"inco" 是一般(薪水之類)收入的別名。使用分類帳別名讓我可以按類別生成收入和費(fèi)用的明細(xì)分類帳。至于記錄的其余部分,其它所有字段都是不需加以說明的!笆欠窀肚?”字段("Y" 或 "N")記錄了交易是否已過帳到我的帳戶;除此之外,還有一個(gè)交易描述,和一個(gè)正的美元金額。
用于計(jì)算當(dāng)前余額的算法不太難。awk 只需要依次讀取每一行。如果列出了費(fèi)用分類帳,但沒有收入分類帳(為 "-"),那么這一項(xiàng)就是借方。如果列出了收入分類帳,但沒有費(fèi)用分類帳(為 "-"),那么這一項(xiàng)就是貸方。而且,如果同時(shí)列出了費(fèi)用和收入分類帳,那么這個(gè)金額就是“分類帳轉(zhuǎn)帳”;即,從費(fèi)用分類帳減去美元金額,并將此金額添加到收入分類帳。此外,所有這些分類帳都是虛擬的,但對(duì)于跟蹤收入和支出以及預(yù)算卻非常有用。
代碼
現(xiàn)在該研究代碼了。我們將從第一行(BEGIN 塊和函數(shù)定義)開始:
balance,第 1 部分
#!/usr/bin/env awk -fBEGIN { FS="\t+" months="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"}function monthdigit(mymonth) { return (index(months,mymonth)+3)/4}
首先執(zhí)行 "chmod +x myscript" 命令,那么將第一行 "#!..." 添加到任何 awk 腳本將使它可以直接從 shell 中執(zhí)行。其余行定義了 BEGIN 塊,在 awk 開始處理支票簿文件之前將執(zhí)行這個(gè)代碼塊。我們將 FS(字段分隔符)設(shè)置成 "\t+",它會(huì)告訴 awk 字段由一個(gè)或多個(gè) tab 分隔。另外,我們定義了字符串 months,下面將出現(xiàn)的 monthdigit() 函數(shù)將使用它。
最后三行顯示了如何定義自己的 awk 。格式很簡(jiǎn)單 -- 輸入 "function",再輸入名稱,然后在括號(hào)中輸入由逗號(hào)分隔的參數(shù)。在此之后,"{ }" 代碼塊包含了您希望這個(gè)函數(shù)執(zhí)行的代碼。所有函數(shù)都可以訪問全局變量(如 months 變量)。另外,awk 提供了 "return" 語句,它允許函數(shù)返回一個(gè)值,并執(zhí)行類似于 C 和其它語言中 "return" 的操作。這個(gè)特定函數(shù)將以 3 個(gè)字母字符串格式表示的月份名稱轉(zhuǎn)換成等價(jià)的數(shù)值。例如,以下代碼:
print monthdigit("Mar"
……將打印:
3
現(xiàn)在,讓我們討論其它一些函數(shù)。
財(cái)務(wù)函數(shù)
以下是其它三個(gè)執(zhí)行簿記的函數(shù)。我們即將見到的主代碼塊將調(diào)用這些函數(shù)之一,按順序處理支票簿文件的每一行,從而將相應(yīng)交易記錄到 awk 數(shù)組中。有三種基本交易,貸方 (doincome)、借方 (doexpense) 和轉(zhuǎn)帳 (dotransfer)。您會(huì)發(fā)現(xiàn)這三個(gè)函數(shù)全都接受一個(gè)自變量,叫作 mybalance。mybalance 是二維數(shù)組的一個(gè)占位符,我們將它作為自變量進(jìn)行傳遞。目前,我們還沒有處理過二維數(shù)組;但是,在下面可以看到,語法非常簡(jiǎn)單。只須用逗號(hào)分隔每一維就行了。
我們將按以下方式將信息記錄到 "mybalance" 中。數(shù)組的第一維從 0 到 12,用于指定月份,0 代表全年。第二維是四個(gè)字母的分類帳,如 "food" 或 "inco";這是我們處理的真實(shí)分類帳。因此,要查找全年食品分類帳的余額,應(yīng)查看 mybalance[0,"food"]。要查找 6 月的收入,應(yīng)查看 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}
調(diào)用 doincome() 或任何其它函數(shù)時(shí),我們將交易記錄到兩個(gè)位置 -- mybalance[0,category] 和 mybalance[curmonth, category],它們分別表示全年的分類帳余額和當(dāng)月的分類帳余額。這讓我們稍后可以輕松地生成年度或月度收入/支出明細(xì)分類帳。
如果研究這些函數(shù),將發(fā)現(xiàn)在我的引用中傳遞了 mybalance 引用的數(shù)組。另外,我們還引用了幾個(gè)全局變量:curmonth,它保存了當(dāng)前記錄所屬的月份的數(shù)值,$2(費(fèi)用分類帳),$3(收入分類帳)和金額($7,美元金額)。調(diào)用 doincome() 和其它函數(shù)時(shí),已經(jīng)為要處理的當(dāng)前記錄(行)正確設(shè)置了所有這些變量。
主塊
以下是主代碼塊,它包含了分析每一行輸入數(shù)據(jù)的代碼。請(qǐng)記住,由于正確設(shè)置了 FS,可以用 $ 1 引用第一個(gè)字段,用 $2 引用第二個(gè)字段,依次類推。調(diào)用 doincome() 和其它函數(shù)時(shí),這些函數(shù)可以從函數(shù)內(nèi)部訪問 curmonth、$2、$3 和金額的當(dāng)前值。請(qǐng)先研究代碼,在代碼之后可以見到我的說明。
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) } }
在主塊中,前兩行將 curmonth 設(shè)置成 1 到 12 之間的整數(shù),并將金額設(shè)置成字段 7(使代碼易于理解)。然后,是四行有趣的代碼,它們將值寫到數(shù)組 globcat 中。globcat,或稱作全局分類帳數(shù)組,用于記錄在文件中遇到的所有分類帳 -- "inco"、"misc"、"food"、"util" 等。例如,如果 $2 == "inco",則將 globcat["inco"] 設(shè)置成 "yes"。稍后,我們可以使用簡(jiǎn)單的 "for (x in globcat)" 循環(huán)來迭代分類帳列表。
在接著的大約二十行中,我們分析字段 $2 和 $3,并適當(dāng)記錄交易。如果 $2=="-" 且 $3!="-",表示我們有收入,因此調(diào)用 doincome()。如果是相反的情況,則調(diào)用 doexpense();如果 $2 和 $3 都包含分類帳,則調(diào)用 dotransfer()。每次我們都將 "balance" 數(shù)組傳遞給這些函數(shù),從而在這些函數(shù)中記錄適當(dāng)?shù)臄?shù)據(jù)。
您還會(huì)發(fā)現(xiàn)幾行代碼說“if ( $5 == "Y" ),那么將同一個(gè)交易記錄到 balance2 中”。我們?cè)谶@里究竟做了些什么?您將回憶起 $5 包含 "Y" 或 "N",并記錄交易是否已經(jīng)過帳到帳戶。由于僅當(dāng)過帳了交易時(shí)我們才將交易記錄到 balance2,因此 balance2 包含了真實(shí)的帳戶余額,而 "balance" 包含了所有交易,不管是否已經(jīng)過帳?梢允褂 balance2 來驗(yàn)證數(shù)據(jù)項(xiàng)(因?yàn)樗鼞?yīng)該與當(dāng)前銀行帳戶余額匹配),可以使用 "balance" 來確保沒有透支帳戶(因?yàn)樗鼤?huì)考慮您開出的尚未兌現(xiàn)的所有支票)。
生成報(bào)表
主塊重復(fù)處理了每一行記錄之后,現(xiàn)在我們有了關(guān)于比較全面的、按分類帳和按月份劃分的借方和貸方記錄,F(xiàn)在,在這種情況下最合適的做法是只須定義生成報(bào)表的 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) }
這個(gè)報(bào)表將打印出匯總,如下所示:
Your available funds:1174.22Your account balance:2399.33
在 END 塊中,我們使用 "for (x in globcat)" 結(jié)構(gòu)來迭代每一個(gè)分類帳,根據(jù)記錄在案的交易結(jié)算主要余額。實(shí)際上,我們結(jié)算兩個(gè)余額,一個(gè)是可用資金,另一個(gè)是帳戶余額。要執(zhí)行程序并處理您在文件 "mycheckbook.txt" 中輸入的財(cái)務(wù)數(shù)據(jù),將以上所有代碼放入文本文件 "balance",執(zhí)行 "chmod +x balance",然后輸入 "./balance mycheckbook.txt"。然后 balance 腳本將合計(jì)所有交易,打印出兩行余額匯總。
升級(jí)
我使用這個(gè)程序的更高級(jí)版本來管理我的個(gè)人和企業(yè)財(cái)務(wù)。我的版本(由于篇幅限制不能在此涵蓋)會(huì)打印出收入和費(fèi)用的月度明細(xì)分類帳,包括年度總合、凈收入和其它許多內(nèi)容。它甚至以 HTML 格式輸出數(shù)據(jù),因此我可以在 Web 瀏覽器中查看它。 如果您認(rèn)為這個(gè)程序有用,我建議您將這些特性添加到這個(gè)腳本中。不必將它配置成要 記錄任何附加信息;所需的全部信息已經(jīng)在 balance 和 balance2 里面了。只要升級(jí) END 塊就萬事具備了!
我希望您喜歡本系列。有關(guān) awk 的詳細(xì)信息,請(qǐng)參考以下列出的參考資料。
參考資料
·如果想看好的老式書籍,O'Reilly 的 sed & awk, 2ndEdition 是極佳選擇。
·請(qǐng)參考 comp.lang.awkFAQ。它還包含許多附加 awk 鏈接。
·Patrick Hartigan 的 awk tutorial 還包括了實(shí)用的 awk 腳本。
·Thompson's TAWKCompiler 將 awk 腳本編譯成快速二進(jìn)制可執(zhí)行文件?捎冒姹居 Windows 版、OS/2 版、DOS 版和 UNIX 版。
·The GNUAwk User's Guide 可用于在線參考。
關(guān)于作者
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的總裁兼 CEO,Gentoo Linux(用于 PC 的高級(jí) Linux)和 Portage 系統(tǒng)(Linux 的下一代移植系統(tǒng))的創(chuàng)始人。他還是 Macmillan 書籍 Caldera OpenLinux Unleashed、SuSE Linux Unleashed 和 Samba Unleashed 的合作者。Daniel 自二年級(jí)起就與計(jì)算機(jī)結(jié)下不解之緣,那時(shí)他首先接觸的是 Logo 程序語言,并沉溺于 Pac-Man 游戲中。這也許就是他至今仍擔(dān)任 SONY Electronic Publishing/Psygnosis 的首席圖形設(shè)計(jì)師的原因所在。Daniel 喜歡與妻子 Mary 和新出生的女兒 Hadassah 一起共度時(shí)光。可通過 drobbins@gentoo.org 與 Daniel 聯(lián)系。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Copyright & ChinaUnix.net * 轉(zhuǎn)載請(qǐng)注明出處及作者 |
|