本章要點: 本章介紹用來替代shell腳本的工具,如TCL和perl。 本章具體包括以下內容。 TCL/expect的使用 awk語言的基本知識 perl語言的基本知識 |
11.1 TCL和expect
TCL是一種類似shell腳本的語言,你可以使用它來完成許多操作。不過,我介紹它的要原因是expect是從它發展出來的。如果你想要寫一個能夠自動處理輸入輸出的腳本(如向用戶提問並且驗證密碼)又不想面對C或者Perl,那麼expect是你的唯一選擇。
11.1.1 TCL語言
要使用TCL,你必須先安裝這個程序:
% rpm -q tcl
tcl-8.0.5-30
TCL語言可以用互動式或者腳本的方式執行,要使用互動式的TCL環境,只要輸入
$ tclsh
%
出現的"%"符號是TCL的提示符,然後就可以使用TCL命令的。
如果你要使用腳本方式的TCL,首先把你的腳本寫成一個文本文件,例如test.tcl,然後執行
$ tclsh test.tcl
在tcl腳本中,每一行或者是一個命令行,或者是一個註釋。註釋行必須以#符號開頭,而命令行最好以分號結束,雖然不一定要這樣做,但是這樣做可以免去不少麻煩。
變數
在tcl中,有兩種基本類型的變數,即標量和數組。標量就是一般的數字或者字元串變數,可以用set語句定義同時賦值:
% set i 1
1
字元串應該用引號括起來:
% set str "test"
'test'
要輸出一個標量的內容,使用put語句:
% puts $str
test
$用來說明str是一個變數。puts函數在標準輸出顯示變數的內容。
數組也可以用set語句定義,實際上,tcl中建立數組只是單個建立數組的元素。例如 ,
% set arr(1) 0
0
% set arr(2) 1
1
這樣就建立了一個兩個元素的數組arr。在TCL中,不存在相當於數組邊界這樣的東西,例如
% set arr(100) to
to
這時數組中實際只存在arr(1),arr(2)和arr(100),這是和C語言不同的地方。用array size命令可以返回數組的大小:
% array size arr
3
訪問數組的方法和訪問標兩實際是一樣的,例如:
% puts $arr(100)
to
可以用同樣的方法創建多維數組。
要使用數組中的所有元素,需要使用一種特殊的便利方式。首先要啟動startsearsh:
% array startsearch arr
s-1-arr
這裡返回了一個搜索id,你可以把它傳遞給某個變數,因為以後還要使用它進行進一步的搜索:
% set my_id [array startsearch arr]
s-1-arr
現在my_id的內容是s-1-arr,然後,就可以搜索arr的內容了:
% array nextelement arr $my_id
whi
這裡的array nextelement返回的是什麼?可能有點出乎你的意料,是arr數組的下標,再執行一次array nextelement命令又會找出另外一個下標:
% array nextelement arr $my_id
4
這樣遍歷下去,可以找出arr數組的所有下標,而知道下標之後,就可以用$arr(4)之類的方式訪問arr的內容了。當遍歷完成之後,array nextelement命令將簡單地返回:
% array nextelement arr $my_id
%
這時就可以停止遍歷過程了,如果你想確認遍歷是否完成,可以使用array anymore命令:
% array anymore arr $my_id
0
返回0說明遍歷已經完成。
串處理
TCL中可以進行一般的串處理過程,這可以使用string命令和append命令,append命令將某個字元串加到另外一個字元串的後面:
% set str1 "test "
test
% set str2 "cook it"
cook it
% append str1 $str2 " and other"
test cook it and other
string命令可以執行字元串的比較,刪除和查詢,其格式是 string [參數] string1 [string2]
參數可以是下面的命令之一:
compare 按照字典順序對字元串進行比較,根據相對關係返回-1,0或者+1。
first 返回string2中第一次出現string1的位置,如果失敗,返回-1。
last 返回string2中最後一次出現string1的位置,如果失敗,返回-1
trim 從string1中刪除開頭和結尾的出現在string2中的字元
trimleft 從string1中刪除開頭的出現在string2中的字元。
trimright 從string1中刪除結尾的出現在string2中的字元
下面幾個用在string中的參數不需要string2變數:
length 返回tring1的長度
tolower 返回將string1全部小寫化的串
toupper 返回將string1全部大寫化的串
運算
TCL的運算方式比較彆扭,它使用expr命令作為計算符號,其用法類似C語言的+=和/= ,例如,
% set j [expr $i/5]
1
注意TCL會自動選擇整數或者浮點計算:
% set l [ expr $i /4.0]
1.25
% set l [ expr $i /4]
1
在TCL裡面可以使用+ - * /和%作為基本運算符,另外通常還包括一些數學函數,如abs,sin,cos,exp和power(乘方)等等。
另外,還有一個起運算符作用的命令incr,它用來對變數加一:
% set i 1
1
% incr i
2
流程式控制制
tcl支持分支和循環。分支語句可以使用if和switch實現。if語句的和C語言類似,如
if { $ x < 0 } {
set y 10;
}
注意判斷子句也需要使用花括弧。
與C語言一樣,tcl的if語句也可以使用else和elseif。
switch語句的用法有點類似這樣:
switch $x {
0 { set y 10;}
10 { set y 100;}
20 { set y 400;}
}
與C的switch語句不同,每次只有符合分支值的子句才被執行。
循環命令主要由for,foreach和while構成,而且每一個都可以使用break和continue子句。
for語句的格式有點類似這樣:
for { set i 0} {$i < 10} { incr i} {puts $i}
將會輸出從1到9的整數。
如果用while循環,這個句子可以寫成
while {$i < 10 } {
puts $i;
incr i;
}
foreach是對於集合中的每一個元素執行一次命令,大致的命令格式是
foreach [變數] { 集合 } {
語句;
}
例如
% foreach j { 1 3 5} {
put $j;
}
1
3
5
函數
如同在一般的編程語言裡面一樣,在tcl裡面也可以定義函數,這是通過proc命令實現的:
proc my_proc {i}{
puts $i;
}
這樣就定義了一個名字叫proc的函數,它只是在終端顯示輸入變元的內容。
要使用這個函數,簡單地輸入它的名字:
% my_proc { 5 }
5
如果變元的數目是0,只要使用空的變元列表,例如 proc my_proc {} {語句;}
儘管tcl還可以處理更複雜的過程,但是我們不再介紹了,例如文件的讀寫以及tk圖形語言,因為我們處理tcl的主要目標就是理解expect,對於更複雜的編程工作,我們建議你使用perl。
11.1.2 expect
expect是建立在tcl基礎上的一個工具,它用來讓一些需要交互的任務自動化地完成。我們首先從一個簡單的例子開始,如同在這一節一開始就提到的,我們想設置一個自動的文件下載程序。
我們看一看這樣的一個例子腳本:
#! /usr/bin/expect
spawn ftp 202.199.248.11
expect "Name"
send "ftp\r"
expect "Password:"
send "nothing\r"
expect "apply"
send "cd /pub/UNIX/Linux/remoteX\r"
expect "successful."
send "bin\r"
expect "set to I"
send "get exceed5.zip\r"
expect "complete."
send "quit\r"
這個是什麼意思?呵呵,就是個自動下載程序。第一行說明這個程序應該調用/usr/bin/expect去執行,然後的就是expect命令。
察看expect的手冊頁面(man expect)可以得到一個很長的expect說明,可惜其中關於expect的語法仍然介紹的不夠。一般來說, expect主要用在需要自動執行人機交互的過程中,例如fsck程序,這個程序會不斷地提問"yes/no",像這樣的命令就可以用expect 來完成。
spawn語句在expect腳本中用於啟動一個新的進程,在我們的程序中,spawn ftp 202.199.248.11就是去執行ftp程序,接下來,就是expect和send的指令對了。
每一對expect和send指令代表一個信息/回應。如果這樣說不好理解的話,那麼可以看一看ftp的具體執行過程:
ftp 202.199.248.11
Connected to 202.199.248.11.
220 mail.asnc.edu.cn FTP server (BeroFTPD 1.3.3(3) Sun Feb 20 15:52:49 CST 2000.
Name (202.199.248.11:wanghy):
顯然,一旦連接成功,伺服器會返回一個Name(202.199.248.11:wanghy):的字元串來要求客戶給出用戶名。expect語句簡單地在返回信息中查詢你給出的字元串,一旦成功就執行下面的命令,現在,expect " Name"已經成功地找到了Name字元串,接下來可以執行 send命令了。
send命令比expect命令更簡單,它簡單地向標準輸入提交你設定的字元串,現在設置為send "ftp\r"表示等到登錄信息之後就給出一個輸入ftp回車,也就是標準的登錄過程。
下面的行與這些行完全一樣,只是機械地等待伺服器的回應,並且提交自己的輸入。
要使用這個expect腳本,你只需要將它設置為可執行的屬性,然後執行它,expect就會執行你需要的服務。
由於expect是tcl的擴展,所以你在expect文件中可以象tcl腳本一樣設置變數和程序流程。
現在我們看一看我們還能夠如何改進我們的expect腳本。ftp命令可能會失敗,比如遠端的機器可能會無法提供服務,或者在啟動ftp命令時本地機器發生問題。為了處理這一類的問題,我們可以使用expect的timeout選項來設置超時的話expect腳本自動退出:
#! /usr/bin/expect
spawn ftp 202.199.248.11
expect {
timeout exit
Connect
}
………………
注意這裡面使用的花括弧。它的含義是使用一組並列表達式。使用並列表達式的主要原因是這樣:如果使用下面的指令對:
expect timeout
exit
那麼由於expect腳本是順序執行的,那麼當程序執行到這個expect的時候就會阻塞,所以程序會一直等待到timeout然後退出。並列表達式則是相當於switch的行為,只要列出的幾項內容有一項得到滿足,expect命令就得到滿足,於是程序可以正常執行。上面的腳本表示,如果連接ftp的時候發生了超時,那麼就退出,否則,一旦發現Connect應答,說明伺服器已經正常了,那麼就可以繼續運行了。
我們可以看看用tcl能夠對我們的expect腳本提供什麼幫助。我們可以設置讓expect腳本不斷地連接遠端伺服器的服務,直到正常建立連接開始,為此,我們可以把建立連接的命令放在一個循環裡面,並且根據回應的不同自動選擇重新輸入命令還是繼續執行:
spawn ftp
while {1} {
expect "ftp>"
send "o 202.199.248.11\r"
expect {
"Connected" break
"refused" { sleep 10} ;
}
}
這裡使用了我們在tcl語言中講到的while和break命令,熟悉C的讀者應該很容易看出它的行為:不斷地等待ftp>提示符,在提示符下面發送連接遠端伺服器的命令,如果伺服器回應是refused(連接失敗),就等待10秒鐘,然後開始下一次循環;如果是Connected,那麼就跳出循環執行下面的命令。sleep是expect的一個標準命令,表示暫停若干秒鐘。
expect還支持許多更複雜的進程式控制制方式,如fork,disconnect等等,你可以從手冊頁面中得到詳細的信息。另外,各種tcl運算符和流程式控制制命令,包括tcl函數也可以使用。
有些讀者可能會問,如果expect執行的話是否控制台輸入不能使用了,答案是否定的。expect命令運行時,如果某個等待的信息沒有得到,那麼程序會阻塞在相應的expect語句處,這時,你在鍵盤上輸入的東西仍然可以正常地傳遞到程序中去,其實對於那些expect處理的信息,原則上你輸入的內容仍然有效,只是expect的反映太快,總是搶在你的前面「輸入」就是了。知道了這一點之後,你就可能寫一個expect腳本,讓expect自動處理來自 fscki的那些噁心的yes/no選項(我們介紹過,這些yes/no其實完全是多餘的,正常情況下你除了選擇yes之外什麼也幹不了)。
預設下,expect在標準輸出(你的終端上)輸出所有來自應用程序的回應信息,你可以用下面的兩個命令重定向這些信息:
log_file [文件名]
這個命令讓expect在你設置的文件中記錄輸出信息。必須注意,這個選項並不影響控制台輸出信息,不過如果你通過crond設置expect腳本在半夜運行的話,你就確實可能需要這個命令來記錄各種信息了。例如:
log_file expect.log
log_user 0/1
這個選項設置是否顯示輸出信息,設置為1時是預設值,為0 的話,expect將不產生任何輸出信息,或者說簡單地過濾掉控制台輸出。必須記住,如果你用log_user 0關閉了控制台輸出,那麼你同時也就關閉了對記錄文件的輸出。
這一點很讓人困擾,如果你確實想要記錄expect的輸出卻不想讓它在控制台上製造垃圾的話,你可以簡單地把expect的輸出重定向到/dev/null:
./test.exp > /dev/null
你可以象下面這樣使用一對fork和disconnect命令。expect的disconnect命令將使得相應的進程到後台執行,輸入和輸出被重定向到/dev/null:
if [fork]!=0 exit
disconnect
fork命令會產生出一個子進程,而且它產生返回值,如果返回的是0,說明這是一個子進程,如果不為0,那麼是父進程。因此,執行了fork命令之後,父進程死亡而子進程被disconnect命令放到後台執行。注意disconnect命令只能對子進程使用。
11.2 awk和文件的處理
UNIX裡面充斥著各種記錄文件和類似的東西。對文本文件的處理是系統管理員每天重要的工作,例如從系統記錄中查找重要的內容,或者對某種程序的輸出進行統計等等。我們將介紹常用的一個處理程序,即gawk。
11.2.1 grep和正則表達式
讓我們首先從grep命令開始。這個命令大家應該很熟悉了,它用來在文件中查找一個字元串。不過,實際上,grep的處理功能要強大和複雜的多。
grep 命令的語法是
grep [模式] [文件名]
如果沒有給出文件名,就預設使用標準輸入。grep每次讀取一行,並且和給出的模式進行匹配,如果成功就把這一行會顯,例如:(粗體的是我們輸入的內容)
$ grep test
close
test my hand
test my hand
grep的「模式」也稱為正則表達式,可以由各種基本的正則表達式元素構成。正則表達式元素主要包括下面幾種:
字元串 匹配任何字元串,例如grep test表示在標準輸入中1
[...] 封閉集中匹配一個字元,如:[abcde]可以匹配a,b,c,d,e
[^...] 求補集中匹配一個字元,例如[^ABC]匹配
. 匹配任意字元
\s 空白符
\S 非空白符
\d 數字
\D 非數字
\w 字母或數字
\W 非字母和數字
* 匹配任何字元
上面的形式是grep中使用的基本正則表達式,另外,還可以使用egrep,egrep是grep的一個擴展版本,支持下面這些擴展的正則字元串:
^ 匹配一行的開始
$ 匹配一行的結尾
( ) 確定正則表達式求值順序,和正常演算中的括弧意思差不多。
(...|...|...) 或,可選項之一進行匹配,例如:(abc|dev|ghi)可以匹配abc,dev,ghi,而(ww|gg)do可以匹配wwdo或者ggdo。
+ 一次或多次模式
如:aba+匹配aba,abaa...不匹配ab
通常,我們有兩種方法使用grep和egrep,一種是使用管道,例如我們應該熟悉的ps ax |grep sendmail,另一種是直接在文件中搜索對應的字元串。
grep/egrep還可以在命令行使用開關,常用的開關包括:
-b 在行前加上塊號
-c 統計匹配行的個數
-n 在行前加上行號
-w 將模式解釋為字元串,所有正則表達式的控制命令失效
-x 精確匹配
-r 查詢文件時包含子目錄
舉個例子來說,我們想在/var/log/httpd/access_log中查詢所有不是來自本地(192.168.0.1)的請求記錄,可以執行:
grep ?v "^192.168.0.1" /var/log/httpd/access_log
^用來讓grep 只在行首匹配。
在grep查詢的時候可以使用通配符代表多個文件,例如,grep start * -r將在當前目錄以及所有子目錄的所有文件中查詢start字元串。
11.2.2 gawk的使用方法
gawk是awk的一個實現,awk是一種用來處理報告等文本文件的腳本語言。不過,我們介紹這個產品的主要目標是用它來處理各種程序的記賬文件。對於複雜的腳本,還是用Perl比較合適。
gawk 的主要功能是針對檔案的每一行搜尋指定的 模式。,每當找到一個匹配的模式,gawk就會去執行你設定的動作。按照這個方式, gawk 依此方式處理輸入檔案的每一行直到輸入檔案結束。如果對於某個模式沒有設置對應的動作,gawk將直接將這個行顯示出來。
為了使用gawk,你通常必須先寫一個awk腳本,除非模式/動作非常簡單,可以在一行上完成。我們用一個例子來解釋gawk的基本用法,首先產生一個目錄列表文件:
ls ?l /etc > list
現在list的內容有點像這樣:
total 2164
drwxr-xr-x 3 root root 4096 Feb 15 22:55 CORBA
-rw-r--r-- 1 root root 2045 Sep 24 1999 DIR_COLORS
-rw-r--r-- 1 root root 17 Mar 25 19:59 HOSTNAME
…………
現在我們選擇一個最簡單的例子,簡單地查找所有屬性是drwxr-xr-x的目錄文件:
gawk '/drwxr-xr-x/ {print $0}' list
將輸出所有這樣的目錄。
這個例子看上去沒有什麼實際用處,因為用grep也可以做同樣的動作,那麼我們可以看一看下面這個功能:
$ gawk '$1=="-rwxr-xr-x" {sum=sum+$5} END {print sum}' list
15041
這個是什麼意思?對於所有屬性是755的文件,讓gawk對第五欄的數字求和。第五欄我們可以看到就是文件的長度,因此這個命令將顯示所有屬性為755的文件的總共的長度。
$n是gawk中非常重要的概念,它用來表示文本串的分欄。預設的情況下,gawk將輸入字元串(從文件中讀入的每一行)按照分割的空格分成若干個欄位,每個欄位作為一個變數,例如有一行
my name is 3th test
那麼,在awk讀入這一行之後,就產生了$1到$5變數,其中$1="my",$2="is",……… ,最後$5="test"。另外還有一個特殊的變數$0,它表示整個輸入行,也就是這個字元串"my name is test"。另外還有一個特殊的變數NF,它表示當前行的欄位的個數,在現在的情況下,NF應該等於5。
在某些特殊的情況下,你可能需要改變分割符的定義,這可以通過對FS賦值來完成,例如FS=","將分割符定義為都號而不是預設的空格。
在一般情況下,gawk可以從命令文件中獲得模式/動作,命令文件的格式很簡單,就是直接將應該寫在命令行上的模式/動作對寫在文件裡面,每個對構成一行,模式可以有兩種,一種是模式匹配,也就是我們在前面解釋的正則表達式,如果使用正則表達式,那麼需要用兩個/把它們夾在一起,例如/[A-Z]/表示正則表達式[A-Z]。
另一種模式是比較指令,比較指令可以用比較操作符和邏輯運算符來構成,常用的比較操作符有:
== 等於 <= 不大於 ~ 按照正則表達式匹配
< 小於 >= 不小於 !~ 按照正則表達式不匹配
> 大於 != 不等於
邏輯運算符有
&& 和 || 或 ! 非 ()括弧
設定了模式后,就可以設置對應的動作了,在gawk中,動作必須用花括弧括起來。ga
wk能完成的動作並不多,畢竟它是一種報告分析語言。一般情況下,只要熟悉print和p
rintf命令就足夠了,print命令的格式非常簡單:
print item1,item2,…………
輸出時,每個項目輸出一欄,中間用空格分開。一個print後面不跟著任何變數會導致gawk顯示當前的輸入行($0)。如果要輸出一個字元串,使用引號把它括起來,特別是如果要輸出一個空行,使用print ""。這裡是一個例子,它將list文件的頭兩欄輸出:
gawk '{print $1,$2}' list
由於輸入的文本文件內容有多行,你在命令欄中設計的模式/動作會對每一行執行一次。就是:
total 2164
drwxr-xr-x 3
-rw-r--r-- 1
-rw-r--r-- 1
-rw-r--r-1
…………………
如果你要精確地控制輸出,也可以使用printf命令,這個命令的格式是:
printf format, item1, item2, ...
format參數就是C語言裡面的格式控制符,例如%c,%d,%f等等。在 % 與格式控制字母之間可加入 modifier,modifier 是用來進一步控制輸出的格式。可能的 modifier 如下所示:
'-' 使用在 width 之前,指明是向左靠齊。如果'-'沒有出現,則會在被指定的寬度向右靠齊。例如:
printf "%-4S", "foo"會印出'foo '。
'width' 這一個數字指示相對應的欄位印出時的寬度。例如:
printf "%4s","foo" 會印出' foo'。
width 的值是一個最小寬度而非最大寬度。如果一個 item 的值需要的寬度比 width 大,則不受 width 的影響。例如printf "%4s","foobar"將印出'foobar'。
'.prec' 此數字指定印出時的精確度。它指定小數點右邊的位數。如果是要印出一個字串,它指定此字串最多會被印出多少個字元。
作為一種腳本語言,gawk允許使用變數,定義變數非常簡單,就是直接用等號對它賦值。為了在gawk程序的開始處對變數賦值,gawk專門提供了BEGIN語句,這個語句將在所有行被讀入之前執行,而且只執行一次,通常用它來執行初始化命令,例如
BEGIN { sum=0;count=0;average=0.0;}
對於變數可以使用數學表達式進行運算,運算符包括常見的加減乘除算符,以及^(乘方),%(取余)和著名的++,--。不過注意gawk在做除法的時候總是使用浮點除法,除了取余算符%。
函數
另外,gawk包含下列函數:
數學函數
atan2(x,y) y/x的正切
cos(x) 餘弦函數
sin(x) 正弦函數
int(x) 取整
log(x) 取自然對數
exp(x) 指數函數
rand(x) 生成一個0到1之間的隨機數
srand() 初始化隨機數發生器
systime() 返回從1970年1月1日0:00到當前時間的秒數
sqrt(x) 取x的平方根
字元串函數
index(string1,string2 )
它會在string1 裡面,尋找string2 第一次出現的地方,返回值是字串string2出現在字串string1 裡面的位置。如果找不到,返回值為 0。
例如:
print index("peanut","an")
會印出 3。
length(string)
string字元串的長度
例如:
length("abcde")
是 5。
match(string,regexp)
match 函數會在字串 string 裡面,尋找符合 regexp 的最長、最靠左邊的子字串。返回值是 regexp 在 string 的開始位置,即 index值。這個函數會設定內部變數 RSTART 等於 index,內部變數RLENGTH 等於符合的子串個數。如果不符合,則會設定 RSTART 為0、RLENGTH 為 -1。
sprintf(format,expression1,...)
跟C語言的sprintf差不多。
例如:
sprintf("pi = %.2f (approx.)',22/7)
傳回的字串為"pi = 3.14 (approx.)"
sub(regexp, replacement,target)
在字串 target 裡面,尋找符合 regexp 的最長、最靠左邊的地方,並且以字串 replacement 代替最左邊的 regexp。
例如:
str = "water, water, everywhere"
sub(/at/, "ith",str)
結果字串str會變成
"wither, water, everywhere"
gsub(regexp, replacement, target)
gsub 與前面的 sub 類似。在字串 target 裡面,尋找符合 regexp 的所有地方,以字串 replacement 代替所有的 regexp。
例如:
str="water, water, everywhere"
gsub(/at/, "ith",str)
結果字串str會變成
'wither, wither, everywhere"
substr(string, start, length)
傳回字串 string 的子字串,這個子字串的長度為 length 個字元,從第 start 個位置開始。
例如:
substr("washington",5,3)
傳回值為"ing"
如果 length 沒有出現,則傳回的子字串是從第 start 個位置開始至結束。
例如:
substr("washington",5)
傳回值為"ington"
tolower(string)
將字串string的大寫字母改為小寫字母。
例如:
tolower("MiXeD cAsE 123")
傳回值為"mixed case 123"
toupper(string)
將字串string的小寫字母改為大寫字母。
例如:
toupper("MiXeD cAsE 123")
傳回值為"MIXED CASE 123"
其他函數
system(command)
此函式允許使用者執行作業系統的指令,執行完畢後將回到 gawk 程式。
例如:
BEGIN {system("ls")}
控制流
在gawk命令腳本中可以使用控制流,主要是if,for,while等語句,用法和C語言相當類似:
if (condition) then-body [else else-body]
如果 condition 為真(true),則執行 then-body,否則執行 else-body。
舉一個例子如下:
if (x % 2 == 0)
print "x is even"
else
print "x is odd"
while (condition)
body
while 語句測試 condition表達式。假如 condition 為真則執行 body 的語句。一次執行完後,會再測試 condition,假如 condition 為真,則 body 會再度被執行。這個過程會一直被重複直到condition 不再是真。如果 condition 第一次測試就是偽(false),則body 從沒有被執行。
下面的例子會印出每個輸入行的前三個欄位。
gawk '{ i=1
while (i <= 3) {
print $i
i++
}
}'
do
body
while (condition)
這個 do loop 執行 body 一次,然後只要 condition 是真則會重複執行 body。即使開始時 condition 是偽,body 也會被執行一次。
下面的例子會印出每個輸入記錄十次。
gawk '{ i= 1
do {
print $0
i++
} while (i <= 10)
}'
for (initialization; condition; increment)
body
此敘述開始時會執行initialization,然後只要 condition是真,它會重複執行body與做increment 。
下面的例子會印出每個輸入記錄的前三個欄位。
gawk '{ for (i=1; i<=3; i++)
print $i
}'
break 會跳出包含它的 for、while、do-while 循環的最內層。
下面的例子會找出任何整數的最小除數,它也會判斷是否為質數。
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 }'
continue 使用於 for、while、do-while 循環內部,它會跳過循環體的剩餘部分,立刻進行下一次循環的執行。
下面的例子會印出 0 至 20 的全部數字,但是 5 並不會被印出。
gawk 'BEGIN {
for (x=0; x<=20; x++) {
if (x==5)
continue
printf ("%d",x)
}
print ""
}'
next 語句強迫 gawk 立刻停止處理目前的行而繼續下一個輸入行。
exit 語句會使得 gawk 程式停止執行而跳出。然而,如果 END 出現,它會去執行 END 的 actions。
自定義函數
你可以定義自己的函數,其格式是
function name (parameter-list) {
body-of-function
}
name 是所定義的函數名字。 parameter-list 是函數的變數列表。變數間使用逗號分開。
函數可以在程序的任何地方定義,不過習慣上總是定義在程序的開頭部分。
下面這個例子,會將每個記錄的第一個欄位之值的平方與第二個欄位之值的平方加起來。
{print "sum =",SquareSum($1,$2)}
function SquareSum(x,y) {
sum=x*x+y*y
return sum
}
如果你熟悉任何編程語言,那麼掌握awk都是很輕鬆的事情,如果你不喜歡它,那麼你可以參考我們下面介紹的perl。
11.3 Perl
Perl 是從awk發展起來的,它由Larry Wall在1986年發明。它是一種功能強大的編程語言,而且可以在許多平台上使用。實際上,你完全可以將 Perl作為一種標準編程語言( 不是腳本語言)來使用,筆者非常喜歡它,並且建議所有不想學習C語言的UNIX管理員應該掌握Perl的基本編程技術。目前,常用的版本是perl 5,幾乎所有的Linux發行版本都會包含它,預設時,linux的perl 5安裝在/usr/bin下,命令是 /usr/bin/perl。