十一. awk程式設計:
1. 變量:
在awk中變量無須定義即可使用,變量在指派時即已經完成了定義。變量的類型可以是數字、字元串。根據使用的不同,未初始化變量的值為0或空白字元串" ",這主要取決于變量應用的上下文。下面為變量的指派負号清單:
符号
含義
等價形式
=
a = 5
+=
a = a + 5
a += 5
-=
a = a - 5
a -= 5
*=
a = a * 5
a *= 5
/=
a = a / 5
a /= 5
%=
a = a % 5
a %= 5
^=
a = a ^ 5
a ^= 5
/> awk '$1 ~ /tom/ {wage = $2 * $3; print wage}' filename
該指令将從檔案中讀取,并查找第一個域字段比對tom的記錄,再将其第二和第三個字段的乘積指派給自定義的wage變量,最後通過print指令将該變量列印輸出。
/> awk ' {$5 = 1000 * $3 / $2; print}' filename
在上面的指令中,如果$5不存在,awk将計算表達式1000 * $3 / $2的值,并将其指派給$5。如果第五個域存在,則用表達式覆寫$5原來的值。
我們同樣也可以在指令行中定義自定義的變量,用法如下:
/> awk -f: -f awkscript month=4 year=2011 filename
這裡的month和year都是自定義變量,且分别被指派為4和2011,在awk的腳本中這些變量将可以被直接使用,他們和腳本中定義的變量在使用上沒有任何差別。
除此之外,awk還提供了一組内建變量(變量名全部大寫),見如下清單:
變量名
變量内容
argc
指令行參數的數量。
argind
指令行正在處理的目前檔案的agv的索引。
argv
指令行參數數組。
convfmt
轉換數字格式。
environ
從shell中傳遞來的包含目前環境變量的數組。
errno
當使用close函數或者通過getline函數讀取的時候,發生的重新定向錯誤的描述資訊就儲存在這個變量中。
fieldwidths
在對記錄進行固定域寬的分割時,可以替代fs的分隔符的清單。
filename
目前的輸入檔案名。
fnr
目前檔案的記錄号。
fs
輸入分隔符,預設是空格。
ignorecase
在正規表達式和字元串操作中關閉大小寫敏感。
nf
目前檔案域的數量。
nr
目前檔案記錄數。
ofmt
數字輸出格式。
ofs
輸出域分隔符。
ors
輸出記錄分隔符。
rlength
通過match函數比對的字元串的長度。
rs
輸入記錄分隔符。
rstart
通過match函數比對的字元串的偏移量。
subsep
下标分隔符。
/> cat employees2
tom jones:4424:5/12/66:543354
mary adams:5346:11/4/63:28765
sally chang:1654:7/22/54:650000
mary black:1683:9/23/44:336500
/> awk -f: '{ignorecase = 1}; $1 == "mary adams" { print nr, $1, $2, $nf}' employees2
2 mary adams 5346 28765
/> awk -f: ' $1 == "mary adams" { print nr, $1, $2, $nf}' employees2
沒有輸出結果。
當ignorecase内置變量的值為非0時,表示在進行字元串操作和處理正規表達式時關閉大小寫敏感。這裡的"mary adams"将比對檔案中的"mary admams"記錄。最後print列印出第一、第二和最後一個域。需要說明的是nf表示目前記錄域的數量,是以$nf将表示最後一個域的值。
awk在動作部分還提供了begin塊和end塊。其中begin動作塊在awk處理任何輸入檔案行之前執行。事實上,begin塊可以在沒有任何輸入檔案的條件下測試。因為在begin塊執行完畢以前awk将不讀取任何輸入檔案。begin塊通常被用來改變内建變量的值,如ofs、rs或fs等。也可以用于初始化自定義變量值,或列印輸出标題。
/> awk 'begin {fs = ":"; ofs = "\t"; ors = "\n\n"} { print $1,$2,$3} filename
上例中awk在處理檔案之前,已經将域分隔符(fs)設定為冒号,輸出檔案域分隔符(ofs)設定為制表符,輸出記錄分隔符(ors)被設定為兩個換行符。begin之後的動作子產品中如果有多個語句,他們之間用分号分隔。
和begin恰恰相反,end子產品中的動作是在整個檔案處理完畢之後被執行的。
/> awk 'end {print "the number of the records is " nr }' filename
awk在處理輸入檔案之後,執行end子產品中的動作,上例中nr的值是讀入的最後一個記錄的記錄号。
/> awk '/mary/{count++} end{print "mary was found " count " times." }' employees2
mary was found 2 times.
/> cat testfile
northwest nw charles main 3.0 .98 3 34
western we sharon gray 5.3 .97 5 23
southwest sw lewis dalsass 2.7 .8 2 18
southern so suan chin 5.1 .95 4 15
southeast se patricia hemenway 4.0 .7 4 17
eastern ea tb savage 4.4 .84 5 20
northeast ne am main jr. 5.1 .94 3 13
north no margot weber 4.5 .89 5 9
central ct ann stephens 5.7 .94 5 13
/> awk '/^north/{count += 1; print count}' testfile #如記錄以正則north開頭,則建立變量count同時增一,再輸出其值。
1
2
3
#這裡隻是輸出前三個字段,其中第七個域先被指派給變量x,再自減一,最後再同時列印出他們。
/> awk 'nr <= 3 {x = $7--; print "x = " x ", $7 = " $7}' testfile
x = 3, $7 = 2
x = 5, $7 = 4
x = 2, $7 = 1
#列印nr(記錄号)的值在2--5之間的記錄。
/> awk 'nr == 2,nr == 5 {print "the record number is " nr}' testfile
the record number is 2
the record number is 3
the record number is 4
the record number is 5
#列印環境變量user和home的值。環境變量的值由父程序shell傳遞給awk程式的。
/> awk 'begin { print environ["user"],environ["home"]}'
root /root
#begin塊兒中對ofs内置變量重新指派了,是以後面的輸出域分隔符改為了\t。
/> awk 'begin { ofs = "\t"}; /^sharon/{ print $1,$2,$7}' testfile
western we 5
#從輸入檔案中找到以north開頭的記錄count就加一,最後在end塊中輸出該變量。
/> awk '/^north/{count++}; end{print count}' testfile
2. 重新定向:
在動作語句中使用shell通用的重定向輸出符号">"就可以完成awk的重定向操作,當使用>的時候,原有檔案将被清空,同時檔案持續打開,直到檔案被明确的關閉或者awk程式終止。來自後面的列印語句的輸出會追加到前面内容的後面。符号">>"用來打開一個檔案但是不清空原有檔案的内容,重定向的輸出隻是被追加到這個檔案的末尾。
/> awk '$4 >= 70 {print $1,$2 > "passing_file"}' filename
#注意這裡的檔案名需要用雙引号括起來。
#通過兩次cat的結果可以看出>和>>的差別。
/> awk '/north/{print $1,$3,$4 > "districts" }' testfile
/> cat districts
northwest joel craig
northeast tj nichols
north val shultz
/> awk '/south/{print $1,$3,$4 >> "districts" }' testfile
southwest chris foster
southern may chin
southeast derek jonhson
awk中對于輸入重定向是通過getline函數來完成的。getline函數的作用是從标準輸入、管道或者目前正在處理的檔案之外的其他輸入檔案獲得輸入。他負責從輸入獲得下一行的内容,并給nf、nr和fnr等内建變量指派。如果得到一個記錄,getline就傳回1,如果達到檔案末尾就傳回0。如果出現錯誤,如打開檔案失敗,就傳回-1。
/> awk 'begin { "date" | getline d; print d}'
tue nov 15 15:31:42 cst 2011
上例中的begin動作子產品中,先執行shell指令date,并通過管道輸出給getline,然後再把輸出指派給自定義變量d并列印輸出它。
/> awk 'begin { "date" | getline d; split(d,mon); print mon[2]}'
nov
上例中date指令通過管道輸出給getline并指派給d變量,再通過内置函數split将d拆分為mon數組,最後print出mon數組的第二個元素。
/> awk 'begin { while("ls" | getline) print}'
employees
employees2
testfile
指令ls的輸出傳遞給getline作為輸入,循環的每個反複,getline都從ls的結果中讀取一行輸入,并把他列印到螢幕。
/> awk 'begin { printf "what is your name? "; \
getline name < "/dev/tty"}\
$1 ~ name {print "found" name " on line ", nr "."}\
end {print "see ya, " name "."}' employees2
what is your name? mary
found mary on line 2.
see ya, mary.
上例先是列印出begin塊中的"what is your name? ",然後等待使用者從/dev/tty輸入,并将讀入的資料指派給name變量,之後再從輸入檔案中讀取記錄,并找到比對輸入變量的記錄并列印出來,最後在end塊中輸出結尾資訊。
/> awk 'begin { while(getline < "/etc/passwd" > 0) lc++; print lc}'
32
awk将逐行讀取/etc/passwd檔案中的内容,在達到檔案末尾之前,計數器lc一直自增1,當到了末尾後列印lc的值。lc的值為/etc/passwd檔案的行數。
由于awk中同時打開的管道隻有一個,那麼在打開下一個管道之前必須關閉它,管道符号右邊可以通過雙引号關閉管道。如果不關閉,它将始終保持打開狀态,直到awk退出。
/> awk {print $1,$2,$3 | "sort -4 +1 -2 +0 -1"} end {close("sort -4 +1 -2 +0 -1") }
filename
上例中end子產品中的close顯示關閉了sort的管道,需要注意的是close中關閉的指令必須和當初打開時的完全比對,否則end子產品産生的輸出會和以前的輸出一起被sort分類。
3. 條件語句:
awk中的條件語句是從c語言中借鑒來的,見如下聲明方式:
if (expression) {
statement;
... ...
}
/> awk '{if ($6 > 50) print $1 "too hign"}' filename
/> awk '{if ($6 > 20 && $6 <= 50) { safe++; print "ok}}' filename
}else {
statement2;
/> awk '{if ($6 > 50) print $1 " too high"; else print "range is ok" }' filename
/> awk '{if ($6 > 50) { count++; print $3 } else { x = 5; print $5 }' filename
statement1;
} else if (expression1) {
} else {
statement3;
/> awk '{if ($6 > 50) print "$6 > 50" else if ($6 > 30) print "$6 > 30" else print "other"}' filename
4. 循環語句:
awk中的循環語句同樣借鑒于c語言,支援while、do/while、for、break、continue,這些關鍵字的語義和c語言中的語義完全相同。
5. 流程控制語句:
next語句是從檔案中讀取下一行,然後從頭開始執行awk腳本。
exit語句用于結束awk程式。它終止對記錄的處理。但是不會略過end子產品,如果exit()語句被指派0--255之間的參數,如exit(1),這個參數就被列印到指令行,以判斷退出成功還是失敗。
6. 數組:
因為awk中數組的下标可以是數字和字母,數組的下标通常被稱為關鍵字(key)。值和關鍵字都存儲在内部的一張針對key/value應用hash的表格裡。由于hash不是順序存儲,是以在顯示數組内容時會發現,它們并不是按照你預料的順序顯示出來的。數組和變量一樣,都是在使用時自動建立的,awk也同樣會自動判斷其存儲的是數字還是字元串。一般而言,awk中的數組用來從記錄中收集資訊,可以用于計算總和、統計單詞以及跟蹤模闆被比對的次數等等。
/> cat employees
tom jones 4424 5/12/66 543354
mary adams 5346 11/4/63 28765
sally chang 1654 7/22/54 650000
billy black 1683 9/23/44 336500
/> awk '{name[x++] = $2}; end{for (i = 0; i < nr; i++) print i, name[i]}' employees
0 jones
1 adams
2 chang
3 black
在上例中,數組name的下标是變量x。awk初始化該變量的值為0,在每次使用後自增1,讀取檔案中的第二個域的值被依次指派給name數組的各個元素。在end子產品中,for循環周遊數組的值。因為下标是關鍵字,是以它不一定從0開始,可以從任何值開始。
#這裡是用内置變量nr作為數組的下标了。
/> awk '{id[nr] = $3}; end {for (x = 1; x <= nr; x++) print id[x]}' employees
4424
5346
1654
1683
awk中還提供了一種special for的循環,見如下聲明:
for (item in arrayname) {
print arrayname[item]
/> cat db
tom jones
mary adams
sally chang
billy black
tom savage
tom chung
reggie steel
tommy tucker
/> awk '/^tom/{name[nr]=$1}; end {for(i = 1;i <= nr; i++) print name[i]}' db
tom
tommy
從輸出結果可以看出,隻有比對正規表達式的記錄的第一個域被指派給數組name的指定下标元素。因為用nr作為下标,是以數組的下标不可能是連續的,是以在end子產品中用傳統的for循環列印時,不存在的元素就列印空字元串了。下面我們看看用special for的方式會有什麼樣的輸出。
/> awk '/^tom/{name[nr]=$1};end{for(i in name) print name[i]}' db
下面我們看一下用字元串作為下标的例子:(如果下标是字元串常量,則需要用雙引号括起來)
/> cat testfile2
mary
sean
bob
alex
/> awk '/tom/{count["tom"]++}; /mary/{count["mary"]++}; end{print "there are " count["tom"] \
" toms and " count["mary"] " marys in the file."} testfile2
there are 2 toms and 4 marys in the file.
在上例中,count數組有兩個元素,下标分别為tom和mary,每一個元素的初始值都是0,沒有tom被比對的時候,count["tom"]就會加一,count["mary"]在比對mary的時候也同樣如此。end子產品中列印出存儲在數組中的各個元素。
/> awk '{count[$1]++}; end{for(name in count) printf "%-5s%d\n",name, count[name]}' testfile2
mary 4
tom 2
alex 1
bob 1
sean 1
在上例中,awk是以記錄的域作為數組count的下标。
/> awk '{count[$1]++; if (count[$1] > 1) name[$1]++}; end{print "the duplicates were "; for(i in name) print i}' testfile2
the duplicates were
在上例中,如count[$1]的元素值大于1的時候,也就是當名字出現多次的時候,一個新的數組name将被初始化,最後列印出那麼數組中重複出現的名字下标。
之前我們介紹的都是如何給數組添加新的元素,并賦予初值,現在我們需要介紹一下如何删除數組中已經存在的元素。要完成這一功能我們需要使用内置函數delete,見如下指令:
/> awk '{count[$1]++}; \
end{for(name in count) {\
if (count[name] == 1)\
delete count[name];\
} \
for (name in count) \
print name}' testfile2
上例中的主要技巧來自end子產品,先是變量count數組,如果數組中某個元素的值等于1,則删除該元素,這樣等同于删除隻出現一次的名字。最後用special for循環列印出數組中仍然存在的元素下标名稱。
最後我們來看一下如何使用指令行參數數組,見如下指令:
/> awk 'begin {for(i = 0; i < argc; i++) printf("argv[%d] is %s.\n",i,argv[i]); printf("the number of arguments, argc=%d\n",argc)}' testfile "peter pan" 12
argv[0] is awk.
argv[1] is testfile.
argv[2] is peter pan.
argv[3] is 12.
the number of arguments, argc=4
從輸出結果可以看出,指令行參數數組argv是以0作為起始下标的,指令行的第一個參數為指令本身(awk),這個使用方式和c語句main函數完全一緻。
/> awk 'begin{name=argv[2]; print "argv[2] is " argv[2]}; $1 ~ name{print $0}' testfile2 "bob"
argv[2] is bob
awk: (filename=testfile2 fnr=9) fatal: cannot open file `bob' for reading (no such file or directory)
先解釋一下以上指令的含義,name變量被指派為指令行的第三個參數,即bob,之後再在輸入檔案中找到比對該變量值的記錄,并列印出該記錄。
在輸出的第二行報出了awk的處理錯誤資訊,這主要是因為awk将bob視為輸入檔案來處理了,然而事實上這個檔案并不存在,下面我們需要做進一步的處理來修正這個問題。
/> awk 'begin{name=argv[2]; print "argv[2] is " argv[2]; delete argv[2]}; $1 ~ name{print $0}' testfile2 "bob"
從輸出結果中我們可以看到我們得到了我們想要的結果。需要注意的是delete函數的調用必要要在begin子產品中完成,因為這時awk還沒有開始讀取指令行參數中指定的檔案。
7. 内建函數:
字元串函數
sub(regular expression,substitution string);
sub(regular expression,substitution string,target string);
/> awk '{sub("tom","tommy"); print}' employees #這裡使用tommy替換了tom。
tommy jones 4424 5/12/66 543354
#當正規表達式tom在第一個域中第一次被比對後,他将被字元串"tommy"替換,如果将sub函數的第三個參數改為$2,将不會有替換發生。
/> awk '{sub("tom","tommy",$1); print}' employees
gsub(regular expression,substitution string);
gsub(regular expression,substitution string,target string);
和sub不同的是,如果第一個參數中正規表達式在記錄中出現多次,那麼gsub将完成多次替換,而sub隻是替換第一次出現的。
index(string,substring)
該函數将傳回第二個參數在第一個參數中出現的位置,偏移量從1開始。
/> awk 'begin{print index("hello","el")}'
length(string)
該函數傳回字元串的長度。
/> awk 'begin{print length("hello")}'
5
substr(string,starting position)
substr(string,starting position,length of string)
該函數傳回第一個參數的子字元串,其截取起始位置為第二個參數(偏移量為1),截取長度為第三個參數,如果沒有該參數,則從第二個參數指定的位置起,直到string的末尾。
/> awk 'begin{name = substr("hello world",2,3); print name}'
ell
match(string,regular expression)
該函數傳回在字元串中正規表達式位置的索引,如果找不到指定的正規表達式就傳回0.match函數設定内置變量rstart為字元串中子字元串的開始位置,rlength為到子字元串末尾的字元個數。
/> awk 'begin{start=match("good ole china", /[a-z]+$/); print start}'
10
上例中的正規表達式[a-z]+$表示在字元串的末尾搜尋連續的大寫字母。在字元串"good ole china"的第10個位置找到字元串"china"。
/> awk 'begin{start=match("good ole china", /[a-z]+$/); print rstart, rlength}'
10 5
rstart表示比對時的起始索引,rlength表示比對的長度。
/> awk 'begin{string="good ole china";start=match(string, /[a-z]+$/); print substr(string,rstart, rlength)}'
china
這裡将match、rstart、rlength和substr巧妙的結合起來了。
toupper(string)
tolower(string)
以上兩個函數分别傳回參數字元串的大寫和小寫的形式。
/> awk 'begin {print toupper("hello"); print tolower("world")}'
hello
world
split(string,array,field seperator)
split(string,array)
該函數使用作為第三個參數的域分隔符把字元串分隔為一個數組。如果第三個參數沒有提供,則使用目前預設的fs值。
/> awk 'begin{split("11/20/2011",date,"/"); print date[2]}'
20
variable = sprintf("string with format specifiers ",expr1,expr2,...)
該函數和printf的差别等同于c語言中printf和sprintf的差别。前者将格式化後的結果輸出到輸出流,而後者輸出到函數的傳回值中。
/> awk 'begin{line = sprintf("%-15s %6.2f ", "hello",4.2); print line}'
hello 4.20
時間函數:
systime()
該函數傳回目前時間距離1970年1月1日之間相差的秒數。
/> awk 'begin{print systime()}'
1321369554
strftime()
時間格式化函數,其格式化規則等同于c語言中的strftime函數提供的規則,見以下清單:
資料格式
%a
abbreviated weekday name
full weekday name
%b
abbreviated month name
full month name
%c
date and time representation appropriate for locale
%d
day of month as decimal number (01 – 31)
%h
hour in 24-hour format (00 – 23)
%i
hour in 12-hour format (01 – 12)
%j
day of year as decimal number (001 – 366)
%m
month as decimal number (01 – 12)
minute as decimal number (00 – 59)
%p
current locale's a.m./p.m. indicator for 12-hour clock
%s
second as decimal number (00 – 59)
%u
week of year as decimal number, with sunday as first day of week (00 – 53)
%w
weekday as decimal number (0 – 6; sunday is 0)
week of year as decimal number, with monday as first day of week (00 – 53)
%x
date representation for current locale
time representation for current locale
%y
year without century, as decimal number (00 – 99)
year with century, as decimal number
/> awk 'begin{ print strftime("%d",systime())}'
11/15/11
/> awk 'begin{ now = strftime("%t"); print now}'
23:17:29
内置數學函數:
名稱
傳回值
atan2(x,y)
y,x範圍内的餘切
cos(x)
餘弦函數
exp(x)
求幂
int(x)
取整
log(x)
自然對數
sin(x)
正弦函數
sqrt(x)
平方根
/> awk 'begin{print 31/3}'
10.3333
/> awk 'begin{print int(31/3)}'
自定義函數:
自定義函數可以放在awk腳本的任何可以放置模闆和動作的地方。
function name(parameter1,parameter2,...) {
statements
return expression
給函數中本地變量傳遞值。隻使用變量的拷貝。數組通過位址或者指針傳遞,是以可以在函數内部直接改變數組元素的值。函數内部使用的任何沒有作為參數傳遞的變量都被看做是全局變量,也就是這些變量對于整個程式都是可見的。如果變量在函數中發生了變化,那麼就是在整個程式中發生了改變。唯一向函數提供本地變量的辦法就是把他們放在參數清單中,這些參數通常被放在清單的最後。如果函數調用沒有提供正式的參數,那麼參數就初始化為空。return語句通常就傳回程式控制并向調用者傳回一個值。
/> cat grades
20 10
30 20
40 30
/> cat add.sc
function add(first,second) {
return first + second
{ print add($1,$2) }
/> awk -f add.sc grades
30
50
70