awk教程入門與實例練習(三)

在 awk 系列的這篇總結中,Daniel 向您介紹 awk 重要的字符串函數,以及演示了如何從頭開始編寫完整的支票簿結算程序。在這個過程中,您將學習如何編寫自己的函數,并使用 awk 的多維數組。學完本文之后,您將掌握更多 awk 經驗,可以讓您創建功能更強大的腳本。

格式化輸出
雖然大多數情況下 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

噢,好吧。雖然不象 Python 的序列類型那樣方便,但 awk 的字符串函數還是可以完成任務。讓我們來看一下。

首先,有一個基本 length() 函數,它返回字符串的長度。以下是它的使用方法:

print length(mystring)

此代碼將打印值:

24

好,繼續。下一個字符串函數叫作 index,它將返回子字符串在另一個字符串中出現的位置,如果沒有找到該字符串則返回 0。使用 mystring,可以按以下方法調用它:

print index(mystring,”you”)

awk 會打印:

9

讓我們繼續討論另外兩個簡單的函數,tolower() 和 toupper()。與您猜想的一樣,這兩個函數將返回字符串并且將所有字符分別轉換成小寫或大寫。請注意,tolower() 和 toupper() 返回新的字符串,不會修改原來的字符串。這段代碼:

print tolower(mystring)
print toupper(mystring)
print mystring

……將產生以下輸出:

how are you doing today?
HOW ARE YOU DOING TODAY?
How are you doing today?

到現在為止一切不錯,但我們究竟如何從字符串中選擇子串,甚至單個字符?那就是使用 substr() 的原因。以下是 substr() 的調用方法:

mysub=substr(mystring,startpos,maxlen)

mystring 應該是要從中抽取子串的字符串變量或文字字符串。startpos 應該設置成起始字符位置,maxlen 應該包含要抽取的字符串的最大長度。請注意,我說的是最大長度;如果 length(mystring) 比 startpos+maxlen 短,那么得到的結果就會被截斷。substr() 不會修改原始字符串,而是返回子串。以下是一個示例:

print substr(mystring,9,3)

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, RLENGTH

awk 將打印:

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)
print mystring
mystring=”How are you doing today?”
gsub(/o/,”O”,mystring)
print mystring

必須將 mystring 復位成其初始值,因為第一個 sub() 調用直接修改了 mystring。在執行時,此代碼將使 awk 輸出:

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,”,”)

調用 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 塊和函數定義)開始:

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
}

首先執行 “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”]。

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
}

調用 doincome() 或任何其它函數時,我們將交易記錄到兩個位置 — mybalance[0,category] 和 mybalance[curmonth, category],它們分別表示全年的分類帳余額和當月的分類帳余額。這讓我們稍后可以輕松地生成年度或月度收入/支出明細分類帳。

如果研究這些函數,將發現在我的引用中傳遞了 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)
}
}

在主塊中,前兩行將 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 塊:

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 塊就萬事具備了!

我希望您喜歡本系列。有關 awk 的詳細信息,請參考以下列出的參考資料。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/535760.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/535760.shtml
英文地址,請注明出處:http://en.pswp.cn/news/535760.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HDFS-常用命令

1. -help:顯示幫助信息 hadoop fs -help rmshel2. -ls:顯示目錄信息 hadoop fs -ls /3. -mkdir:在HDFS上創建目錄 hadoop fs -mkdir -p /user/ha4. -moveFromLocal:從本地剪切粘貼到HDFS hadoop fs -moveFromLocal ~/test.txt…

如何關閉WINDOWS2003 DEP數據保護功能

近來很多朋友和客戶都使用了WINDOWS2003來架設自己的GAME SERVER,但有很多朋友反映說,不如WINDOWS2000好,原因不是穩定,而是成功率高,和簡單.但我個人覺得WINDOWS2003還是不錯的系統,如果朋友們都不用這個系統,而用WINDOWS2000 有點不值得了.我就開始找尋這樣的問題.我對GAME 不…

JDK源碼解析之 java.lang.Thread

位于java.lang包下的Thread類是非常重要的線程類,它實現了Runnable接口,今天我們來學習一下Thread類,在學習Thread類之前,先介紹與線程相關知識:線程的幾種狀態、上下文切換,然后接著介紹Thread類中的方法的…

TASKLIST

TASKLIST [/S system [/U username [/P [password]]]] [/M [module] | /SVC | /V] [/FI filter] [/FO format] [/NH]參數列表:/S system 指定連接到的遠程系統。/U [domain/]user 指定使用哪個用戶執行這個命令。/P [password] 為指定的用戶指定密碼。/SVC 顯示每個進程中的服務…

JDK源碼解析之 java.lang.ThreadLocal

此類提供線程局部變量。這些變量與普通變量不同,每個訪問一個線程(通過其get或set方法)的線程 都有其自己的,獨立初始化的變量副本。 ThreadLocal實例通常是希望將狀態與線程關聯的類中的私有靜態字段(例如&#xff0c…

華爾街頂級大師胡立陽名言

1.不要聽“親朋好友”的話,他們只會讓你成為“平凡人”。 2.不要只會“用功讀書”,重要的是“要讀對書”。  3.不要只是“努力工作”,重要的是“做對工作”。   4.不要指示結交“志趣相投”的朋友,否則你永遠只看到…

JDK源碼解析之 Java.lang.Enum

Enum是一個特殊的類. 我們不能以class Xxx extends Enum的方式手動繼承, 必須寫成enum Xxx的形式; 然而這段枚舉類的定義在編譯之后又變回了class Xxx extends Enum. 一、類定義 public abstract class Enum<E extends Enum<E>>implements Comparable<E>, …

Linux下的一些簡單網絡配置命令介紹

1、 ifconfig可以使用ifconfig命令來配置并查看網絡接口的配置情況。例如&#xff1a;&#xff08;1&#xff09; 配置eth0的IP地址&#xff0c; 同時激活該設備。#ifconfig eth0 192.168.1.10 netmask 255.255.255.0 up&#xff08;2&#xff09; 配置eth0別名設備eth0:1的IP地…

JDK源碼解析之 java.lang.Throwable

在 Java 中&#xff0c;所有的異常都有一個共同的祖先 Throwable&#xff08;可拋出&#xff09;。Throwable 指定代碼中可用異常傳播機制通過 Java 應用程序傳輸的任何問題的共性。 一、類定義 public class Throwable implements Serializable {}Serializable&#xff1a;可…

JDK源碼解析之 java.lang.Error

java.lang.Error 錯誤。是所有錯誤的基類&#xff0c;用于標識嚴重的程序運行問題。這些問題通常描述一些不應被應用程序捕獲的反常情況。 一、源碼部分 //繼承了java.lang.Throwable public class Error extends Throwable {//適用于java序列化機制,過判斷類的serialVersionU…

linux命令之有關網絡的操作命令

1&#xff0e;hostname 命令&#xff08;1&#xff09;一般格式&#xff1a;hostname [選項] [主機名]&#xff08;2&#xff09;說明&#xff1a;顯示或設置系統的主機名&#xff1b;如果無任何選項和主機名&#xff0c;則用于顯示系統的主機名。&#xff08;3&#xff09…

JDK源碼解析之 java.lang.Exception

異常。是所有異常的基類&#xff0c;用于標識一般的程序運行問題。這些問題通常描述一些會被應用程序捕獲的反常情況。 一、源碼部分 //繼承了java.lang.Throwable public class Exception extends Throwable {//適用于java序列化機制,過判斷類的serialVersionUID來驗證的版本…

linux命令之有關關機和查看系統信息的命令

shutdown 正常關機 reboot 重啟計算機 ps 查看目前程序執行的情況top 查看目前程序執行的情景和內存使用情況kill 終止一個進程date 更改或查看目前時間 一&#xff0e;查看系統的進程 要管理進程&#xff0c;首先要知…

HDFS-文件讀寫過程

一、文件讀取 Client向NameNode發起RPC請求&#xff0c;來確定請求文件block所在的位置&#xff1b;NameNode會視情況返回文件的部分或者全部block列表&#xff0c;對于每個block&#xff0c;NameNode 都會返回含有該 block 副本的 DataNode 地址&#xff1b; 這些返回的 DN 地…

linux命令復習之有關磁盤空間的命令

1&#xff0e;mount 命令&#xff08;1&#xff09;一般格式&#xff1a;mount 文件系統類型 [選項] 掛接設備&#xff08;2&#xff09;說明&#xff1a;將某個文件系統掛載到某個目錄上。當這個命令執行成功后&#xff0c;直到使用 umount 將這個文件系統移除為止。&…

HDFS-常用API操作

一、Maven <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>RELEASE</version> </dependency> <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>…

linux命令之-管理文件和目錄的命令

一. 創建和刪除目錄的命令 1&#xff0e;mkdir 命令 &#xff08;1&#xff09;一般格式&#xff1a;mkdir [選項] 目錄名 &#xff08;2&#xff09;說明&#xff1a;該命令創建由目錄名命名的目錄。 &#xff08;3&#xff09;舉例1&#xff1a; 在目錄 /usr/fedora 下建…

Hive-簡介入門

Hive簡介 Hive最初是Facebook為了滿足對海量社交網絡數據的管理和機器學習的需求而產生和發展的。互聯網現在進入了大數據時代&#xff0c;大數據是現在互聯網的趨勢&#xff0c;而hadoop就是大數據時代里的核心技術&#xff0c;但是hadoop的mapreduce操作專業性太強&#xff0…

Hive-原理解析

一、Hive 架構 下面是Hive的架構圖。 Hive的體系結構可以分為以下幾部分 1、用戶接口&#xff1a;CLI&#xff08;hive shell&#xff09;&#xff1b;JDBC&#xff08;java訪問Hive&#xff09;&#xff1b;WEBUI&#xff08;瀏覽器訪問Hive&#xff09; 2、元數據&#x…

linux命令之history命令

在Linux系統上輸入命令并按下Enter后&#xff0c;這個命令就會存放在命令記錄表 ( ~/.bash_history )中&#xff0c;預定的記錄為1000條&#xff0c;這些都定義在環境變量中。列出所有的歷史記錄&#xff1a;#history 只列出最近10條記錄&#xff1a;#history 10 (注,history和…