天天看點

Linux Shell常用技巧(五)

十一. 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