Linux文本處理三劍客之awk學習筆記07:語法

語法

變量

我覺得awk應該算是屬於動態編程語言。其變量不需要事先聲明纔可以使用。我們想要使用的時候隨時引用即可,不需要事先聲明其數據類型。

awk的變量具有三種狀態。

  • 未聲明狀態(untyped)。沒有引用也沒有賦值。
  • 未賦值狀態(unassigned)。引用但還未賦值。
  • 已賦值狀態。

引用未賦值的變量,其初始值爲空字符串或者數字0。

從gnu awk 4.2.0版本開始提供了typeof()函數來判斷一個變量的類型。

# awk 'BEGIN{print typeof(a)}'
untyped
# awk 'BEGIN{a;print typeof(a)}'
unassigned
# awk 'BEGIN{a=3;print typeof(a)}'
number
# awk 'BEGIN{a="alongdidi";print typeof(a)}'
string

如果是4.2.0之前的版本,可以使用如下方法判斷變量狀態。

awk 'BEGIN {
    if(a==""&&a==0) {
        print "Untyped or unassigned."
    } else {
        print "Assigned."
    }
}'    

變量賦值

在awk對變量進行賦值可以看作是一個有返回值的表達式(expression)。

# awk 'BEGIN{print a=3}'
3

它等價於:

awk 'BEGIN{a=3;print a}'

基於變量賦值可以返回值的特點,可以作連續的變量賦值。

x=y=z=5
# 等價於
z=5;y=5;x=5

可以將賦值語句放在允許使用表達式來評估值的情況。

# awk 'BEGIN{if(a=1){print "True"}else{print "False"}}'
True
# awk 'BEGIN{if(a=0){print "True"}else{print "False"}}'
False

如果代碼邏輯比較複雜,不建議將變量賦值語句放入表達式。

# awk 'BEGIN{a=1;arr[a+=2]=a=a+6;print arr[9];print arr[3]}'
7
# 空

這裏不好確定先計算等號(紅色加粗)左邊的數組索引賦值還是先計算等號右邊的變量賦值,可能會根據不同的awk版本而不同。

變量使用

變量可以在三個位置進行賦值。

awk -v var=val [-v var=val ...] '{code}' var=val file1 var=val file2
  1. 在-v選項中賦值,如果賦值多個需要使用多個-v選項。
  2. 在代碼塊中賦值。
  3. 在文件前賦值。

不同位置賦值的變量的作用域會有所不同。例如在main代碼塊中賦值的變量在BEGIN中就無法引用,這點根據awk的工作流程即可得出。

在文件前進行變量賦值,在某些情況下適合於修改FS。

awk '{...}' FS=" " a.txt FS=":" /etc/passwd

awk還可以引用shell當中的變量。

# name="alongdidi"
# awk -v nameAwk=$name 'BEGIN{print nameAwk}'
alongdidi
# awk 'BEGIN{print nameAwk='\"$name\"'}'
alongdidi
# awk '{print nameAwk}' nameAwk=$name a.txt 
alongdidi
...

數據類型

awk有兩種基本的數據類型:字符串和數值。從4.2.0開始,還支持正則表達式類型。

數據是什麼類型不能只看字面意義,例如看到數字不代表它就是數值類型。而是要看數據所處的上下文:在字符串操作環境下轉換成字符串類型,在數值操作環境下轉換爲數值類型。

轉換分爲隱式轉換和顯式轉換。

隱式轉換

1、對數據進行算術運算可以將其轉換爲數值類型。

對於可轉換成數值的數據會正確轉換成數值,例如:“123”、“123abc”和“   123abc”。對於無法正確轉換成數值的數據則轉換成數值0,例如:“abc123”。

# awk 'BEGIN{a="123";print a+0;print typeof(a+0)}'
123
number
# awk 'BEGIN{a="123abc";print a+0;print typeof(a+0)}'
123
number
# awk 'BEGIN{a="   123abc";print a+0;print typeof(a+0)}'
123
number
# awk 'BEGIN{a="abc123";print a+0;print typeof(a+0)}'
0
number

算術運算不止有加法,四則運算均可以。"string"+0比較常用,在不改變數值大小的情況下轉換成數值。

2、對數據進行字符串連接操作可以將其轉換成字符串類型。

使用空格和雙引號連接。

# awk 'BEGIN{print typeof(123"")}'
string
# awk 'BEGIN{print typeof(123 123)}'
string

變量a和b是數值,使用空格連接後隱式轉換成string,再使用加法運算後隱式轉換成number。

# awk 'BEGIN{a=2;b=3;print a b}'
23
# awk 'BEGIN{a=2;b=3;print (a b)+4}'
27
# awk 'BEGIN{a=2;b=3;print typeof((a b)+4)}'
number

顯式轉換

1、使用函數sprintf()基於預定義變量CONVFMT將數值轉換成字符串。

CONVFMT的默認值是%.6g。

# awk 'BEGIN{print CONVFMT}'
%.6g

變量a是小數,遇到""字符串連接時基於CONVFMT的值隱式轉換成字符串"123.46",然後再由print基於OFMT轉換後輸出。

# awk 'BEGIN{a=123.4567;print a""}'
123.457
# awk 'BEGIN{a=123.4567;CONVFMT="%.2f";print a""}'
123.46

2、使用strtonum()函數顯式地將字符串轉換成數字。

# awk 'BEGIN{a="100";print strtonum(a);print typeof(strtonum(a))}'
100
number
# awk 'BEGIN{a="abc";print strtonum(a);print typeof(strtonum(a))}'
0
number

字面量

awk中有3種字面量,剛好對應於3種變量的數據類型。

  • 字符串字面量。
  • 數值字面量。
  • 正則表達式字面量。

字面量的含義就是表示其字面含義,不會再引用其他東西。

數值字面量

整數、浮點數、科學計數法所表示的數字均爲數值字面量,但是千萬不能使用雙引號包裹,否則就是字符串字面量了。

123
123.00
1.23e+8
1.23e-06

awk內部總是以浮點數的形式保存數值。如果在輸出的時候發現數值是一個整數,則會自動丟棄小數部分。

# awk 'BEGIN{a=10.0;print a}'
10

算術運算

以下算術運算符的優先級由高至低。

++ --        自增、自減。
^ **         冪運算(乘方)。
+ -          一元運算符,表示數字的正負性。
* / %        乘法、除法和取模。
+ -          二元運算符,加法和減法。

和其他編程語言一樣,自增和自減運算,在變量出現的位置不同時有不同的效果,我們以自增運算爲例進行闡述。

a++:先引用a的值參與運算,再對a進行自增。

++a:先對a進行自增,再引用a的值參與運算。

當自增運算獨立作爲語句存在時,二者沒有區別。

# awk 'BEGIN{a=3;a++;print a}'
4
# awk 'BEGIN{a=3;++a;print a}'
4

當它們參與表達式的時候,情況則不同了。

# awk 'BEGIN{a=3;print a++;print a}'
3
4
# awk 'BEGIN{a=3;print ++a;print a}'
4
4

冪運算。^是冪運算的符號,這個是符合POSIX標準的,而**在有些版本的awk則無法使用,因此推薦僅使用^。

# awk 'BEGIN{print 2^3}'
8

冪運算的運算順序是從右往左,而不是從左往右。因此下面這個示例中的值是512而不是64。

# awk 'BEGIN{print 2^3^2}'
512

賦值操作符的優先級是最低的,低於上述的算術運算符。

= += -= *= /= %= ^= **=

不建議書寫一些容易引起歧義的語句。因爲不同的awk版本可能有不同的結果。

# awk 'BEGIN{b=3;print b+=b++}'
7

字符串字面量

在awk中以雙引號包裹的均爲字符串字面量。不能以單引號包裹。

"alongdidi"
"29"
"\n"
" "
""

awk中沒有爲字符串的連接提供專門的操作符。只需要將字符串緊緊靠在一起或者使用空格(可多個)分隔即可。

# awk 'BEGIN{print "abc""def"}'
abcdef
# awk 'BEGIN{print "abc" "def"}'
abcdef
# awk 'BEGIN{print "abc"     "def"}'
abcdef

如果是字符串變量的話,則不能靠在一起,否則它們會組合成另一個變量。

# awk 'BEGIN{a="abc";b="def";print ab}'

字符串的串聯也具備優先級概念,其(空格的)優先級低於加減運算。因此如下第一個示例中只有字符串的串聯(兩個數值通過空格字符串聯成字符串),串聯成功。

# awk 'BEGIN{print 12 " " 23}'
12 23
# awk 'BEGIN{print 12 " " -23}'
12-23
# 如果串聯成功的話應該是這樣的
12 -23

第二個示例,由於減法運算的優先級高所以先串聯" "和-23,識別爲二元減法運算等同於0-23等於數值-23,再將數值12和數值-23串聯(因爲中間有空格所以串聯)。所以串聯的結果是“12-23”而不是“12 -23”。

關於操作符的優先級,在下文會有詳述。

正則表達式字面量

正則此前我們已經接觸過了。形如這樣的就是正則的字面量。

/Alice/
/A.*e/
/[[:alnum:]]+/

正則使用在pattern中。匹配方式如下。

"str"~/pattern/
"str"!~/pattern/

這裏的字符串字面量"str"我們也可以使用變量來替代,常使用$0或者$N等。

如果/pattern/單獨出現,則等同於$0~/pattern/。

正則匹配結果有返回值,匹配成功返回1,匹配失敗返回0。

因此在使用正則是會有一些需要注意的點。

1、下面兩個是等價的,a的值永遠只會是0或者1,而不會保存正則字面量來用於後續的引用。下文會詳述如何使用變量來保存正則字面量。

a=/pattern/
a=$0~/pattern/

2、下面三個是逐步等價的,不過一般也不會這麼寫(/pattern/~$1)就是了。需要明白這個過程,因爲一般awk不會報錯。

/pattern/~$1
$0~/pattern/~$1
0/1~$1

3、期望把正則字面量作爲參數傳遞給函數。

a=/Alice/
func(arg1,a)

其實傳遞過去的a的值,只會是0或者1而已。

除了這3個以外還會有很多需要注意的點,主要都是源於/pattern/是$0~/pattern/的縮寫以及不能直接將正則字面量賦值給變量來使用。

想要將正則字面量賦值給變量,必須使用4.2.0版本。從該版本開始變量的數據類型新增了正則類型。使用方式是在賦值時,在正則字面量前面加一個@。

# awk 'BEGIN{a=@/Alice/;print typeof(a)}' a.txt
regexp
# awk 'BEGIN{a=@/Alice/}$0~a{print}' a.txt
2   Alice   female  24   [email protected]  18084925203

當使用了正則類型的變量時,就不能簡寫正則匹配了。

a=@/pattern/
$0~a{action}    # 正確匹配
a{action}    # 錯誤匹配

因此這樣子就無法只打印Alice所在行了。

awk 'BEGIN{a=@/Alice/}a{print}' a.txt

gawk支持的正則表達式

.:匹配任意單個字符,在gawk中包括了換行符。

^:匹配行首。

$:匹配行尾。

[...]:匹配中括號內的任意單個字符。

[^...]:匹配中括號外的任意單個字符。

|:邏輯或,二選一。

+:匹配前一個字符至少一次。

*:匹配前一個字符任意次(0次、1次或者多次)。

?:匹配前一個字符0次或者1次。

():分組捕獲用於反向引用。

{m}:精確匹配前一個字符m次。

{m,}:匹配前一個字符至少m次。

{m,n}:匹配前一個字符m到n次。

{,n}:匹配前一個字符至多n次。

[:lower:]:小寫字母。

[:upper:]:大寫字母。

[:alpha:]:字母(大小寫均可)。

[:digit:]:數字。

[:alnum:]:字母或者數字。

[:xdigit:]:十六進制字符。

[:blank:]:空格或者製表(TAB)字符。

[:space:]:空格字符,包含空格、TAB、換行、回車(carriage return)、進紙(form feed)和垂直(vertical)TAB。

[:punct:]:標點符號字符。非字母、數字、控制和空格字符。

[:graph:]:既可打印又可見的字符。比如字母是即可打印又可見,但是空格是可打印但是不可見的。

[:print:]:可打印的字符,即非控制字符。

[:cntrl:]:控制字符。

以下是gawk所支持的正則元字符。

\y:單詞的起始或者結束部分的空字符,即單詞的左邊界或者右邊界。\yballs?\y,可以匹配單詞ball或者單詞balls。

\B:單詞內部字符之間的空字符。比如cr\Bea\Bte,可以匹配create不能匹配“crea te”。

\<:單詞的左邊界。

\>:單詞的右邊界。

\s:任意單個空白字符,等同於[[:space:]]。

\S:任意單個非空白字符,等同於[^[:space:]]。

\w:任意單個字母、數字或者下劃線,等同於[[:alnum:]_],這三種字符也是單詞的組成成分。

\W:等同於\w的取反[^[:alnum:]_]。

\`:絕對行首。例如當遇到“abc\ndef\nghi”,行首和行尾各有3個位置,而絕對行首隻會是a的前面,絕對行尾只會是i的後面。

\':絕對行尾。

awk中不支持正則的修飾符,因此若想要忽略大小寫進行匹配,就需要先將其統一轉換成大/小寫再來匹配;或者預設預定義變量IGNORECASE。

# awk '$2~/alice/{print}' a.txt 
# awk 'tolower($2)~/alice/{print}' a.txt 
2   Alice   female  24   [email protected]  18084925203
# awk 'BEGIN{IGNORECASE=1}$2~/alice/{print}' a.txt 
2   Alice   female  24   [email protected]  18084925203

awk布爾值

在awk中沒有像其他編程語言那樣提供true和false關鍵詞來表示布爾值。不過它的布爾邏輯卻十分簡單:

  • 數值0表示布爾假。
  • 空字符串表示布爾假。
  • 其他情況均表示布爾真。注意,"0"表示布爾真,因爲它是非空字符串而不是數值0。

上面我們說了,正則匹配有返回值,匹配成功返回1,匹配失敗返回0。

布爾運算也有返回值,布爾真返回1,布爾假返回0。

# awk 'BEGIN{if(0){print "True"}}'
# awk 'BEGIN{if(""){print "True"}}'
# awk 'BEGIN{if("0"){print "True"}}'
True
# awk 'BEGIN{if("alongdidi"){print "True"}}'
True
# awk 'BEGIN{if(100){print "True"}}'
True
# awk 'BEGIN{if(a=100){print "True"}}'    # 賦值操作有返回值,此處返回數值100,布爾真。
True
# awk 'BEGIN{if(a==100){print "True"}}'
# awk 'BEGIN{if("alongdidi"~/a+/){print "True"}}'    # 正則匹配成功返回1,布爾真。
True

awk中比較操作

strnum類型

awk中數據的來源:

  1. 內部產生。包括變量的賦值、表達式或者函數的返回值等。
  2. 外部數據。來源於外部的數據如讀取的文件,用戶的輸入等。

在不考慮gawk 4.2.0版本開始引入的正則表達式類型的情況下,awk基本的數據類型爲字符串(string)和數值(number or numeric)。對於來自外部的數據(例如從文件中讀取的數據),它們理論上應該都是字符串類型。但是有一些字符串類型的數據看起來又是數值的形式,例如a.txt中,從第2行開始的$1、$4和$NF。對於這種類型的數據有時候需要將它們視爲數值而有的時候需要將它們視爲字符串。

因此POSIX定義了一種叫做“numeric string”的類型來表示此類數據。在gawk中使用strnum數據類型來表示。當獲取到的用戶數據看起來像數值時,它是strnum類型,在使用的時候將其視爲數值類型。

注意:數據類型strnum只針對於awk中除了數據常量、字符串常量和表達式計算結果以外的數據。例如從文件中讀取的字段、數組中的元素等等。

雖然來源於管道的外部數據原本應該均被識別爲string,但是由於某些看起來形似數值因此在某些情況下被識別爲了strnum。

# echo "30" | awk '{print typeof($0)}'
strnum
# echo "+30" | awk '{print typeof($0)}'
strnum
# echo " 30" | awk '{print typeof($0)}'
strnum
# echo " +30" | awk '{print typeof($0)}'
strnum
# echo "30a" | awk '{print typeof($0)}'
string
# echo "a30" | awk '{print typeof($0)}'
string
# echo "30 a" | awk '{print typeof($0),typeof($1)}'
string strnum

大小比較操作

比較操作符。

<, >, <=, >=, !=, ==:大小、等值比較。
in:數組成員測試。

比較規則。

        +----------------------------------------------
        |       STRING          NUMERIC         STRNUM
--------+----------------------------------------------
STRING  |       string          string          string
NUMERIC |       string          numeric         numeric
STRNUM  |       string          numeric         numeric
--------+----------------------------------------------

簡而言之,string的優先級最高,一旦操作符兩邊有一方是string,則雙方採用string類型比較。否則其他情況下均採用numeric類型比較。

我們輸出$0和$1以及它們對應的數據類型。雖然是外部數據理應均爲string但是由於形似數值,因此均被識別爲strnum。

# echo ' +3.14' | awk '{print "---"$0"---";print "---"$1"---"}' 
--- +3.14---
---+3.14---
# echo ' +3.14' | awk '{print typeof($0);print typeof($1)}'
strnum
strnum

第一組比較:在這組比較中,strnum和string進行比較,只要其中一方是string則雙方採用string方式比較。字符串逐字符進行比較,因此第一對真,後兩對假。

# echo ' +3.14' | awk '{print($0==" +3.14")}'
1
# echo ' +3.14' | awk '{print($0=="+3.14")}'
0
# echo ' +3.14' | awk '{print($0=="3.14")}'
0

第二組比較:在這組比較中,$0和$1均爲strnum類型數據,差別只在一個空格字符,strnum和numeric進行比較按照numeric方式進行比較。因此$0和$1均按照數值3.14來比較,因此兩對比較返回布爾真。

# echo ' +3.14' | awk '{print($0==3.14)}'
1
# echo ' +3.14' | awk '{print($1==3.14)}'
1

第三組比較:這組和第一組的比較原理其實相同,第一組看懂了,這裏就懂了,因此就不做解釋了。

# echo ' +3.14' | awk '{print($1==" +3.14")}'
0
# echo ' +3.14' | awk '{print($1=="+3.14")}'
1
# echo ' +3.14' | awk '{print($1=="3.14")}'
0

第四組比較:awk可以識別echo通過管道傳遞過來的科學計數表示法的數值1e2,並將其識別爲strnum,然後進行strnum和strnum的比較,按照numeric進行比較,結果顯而易見。對於三目運算符“?:”不懂的,後續也有解釋。

# echo 1e2 3 | awk '{print $1;print $2;print typeof($1);print typeof($2)}'
1e2
3
strnum
strnum
# echo 1e2 3 | awk '{print ($1>$2)?"True":"False"}'
True

採用字符串比較時需要注意,它是按照(應該吧)ASCII編碼表逐字符一一比較。

圖形1的ASCII編碼(十進制)是49,9是57,a是97。

# 以下均爲true。
awk 'BEGIN{print("1"<9)}'
awk 'BEGIN{print(9<"a")}'
awk 'BEGIN{print(1<"a")}'
awk 'BEGIN{print(999<"a11")}'

邏輯運算

expr1 && expr2:邏輯與,二元運算符。如果expr1爲假則無需計算expr2(即短路運算),直接判定整個邏輯與表達式爲假。

expr1 || expr2:邏輯或,二元運算符。如果expr1爲真則無需計算expr2(即短路運算),直接判定整個邏輯或表達式爲真。

! expr:邏輯取反(非),一元運算符。

可以使用這個“!”或者“!!”將數據轉換成布爾值0或者1。

awk 'BEGIN{print(!99)}'    # 0
awk 'BEGIN{print(!"ab")}'    # 0
awk 'BEGIN{print(!0)}'    # 1
awk 'BEGIN{print(!ab)}'    # 1
awk 'BEGIN{print(!"")}'    # 1

awk 'BEGIN{print(!!99)}'    # 1
awk 'BEGIN{print(!!"ab")}'    # 1
awk 'BEGIN{print(!!0)}'    # 0
awk 'BEGIN{print(!!ab)}'    # 0
awk 'BEGIN{print(!!"")}'    # 0

由於變量在初次使用且未賦值的情況下,其值爲空字符串或者數值0,因此其表示的布爾值是假。將這個特性與取反操作相結合,我們可以實現對指定範圍的數據進行處理。思路是:

  1. 設置符合條件的範圍起始位置,使用var=!var使得var的布爾值爲真。注意,這裏的var可以是任意變量,只要是未賦值(或者空字符串或者數值0)即可。
  2. 將var作爲pattern條件進行數據處理。
  3. 處理結束後在範圍終止位置使用var=!var使得var的布爾值爲假。

例如,打印a.txt的第1行至第4行數據。我們可以使用以往使用的方式實現。

awk 'NR<=4{print}' a.txt
awk 'NR>=1&&NR<=4{print}' a.txt

或者使用我們剛說的思路實現。

# awk 'NR==1{print;along=!along;next}along{print}NR==4{along=!along;next}' a.txt 
ID  name    gender  age  email          phone
1   Bob     male    28   [email protected]     18023394012
2   Alice   female  24   [email protected]  18084925203
3   Tony    male    21   [email protected]    17048792503

也可以使用字符串連接存儲到變量中後,在最後(END)再打印。

awk 'NR==1{print;along=!along;next}along{multiLine=multiLine$0"\n"}NR==4{along=!along;next}END{printf multiLine}' a.txt

運算符優先級

運算/操作符(operator)優先級由高至低排列。

()
$
++ --
+ - !
* / %
+ -
space
| |&
< > <= >= != ==
~ !~
in
&&
||
?:
= += -= *= /= %= ^=

()優先級最高;一元運算符的優先級高於大多數二元運算符;space是空格,表示字符串連接,在字符串字面量的字符串串聯中我們已經解釋過字符串串聯優先級對於串聯結果的影響了。

對於優先級相同的運算符,運算順序是從左往右,不過對於賦值和冪運算來說是從右往左運算。

a-b+c  ==>  (a-b)+c
a=b=c  ==>  a=(b=c)
2**2**3  ==>  2**(2**3)

>除了是大於號,也表示輸出重定向,一般來說它的運算符應保持最低纔不會出現意外的結果。

awk 'BEGIN{print "foo">1<2?"true.txt":"false.txt"}'
awk 'BEGIN{print "foo">(1<2?"true.txt":"false.txt")}'

流程控制語句

這部分其實大多數的編程語言都是一樣的,我的博客中的bash的學習筆記對於這些基本的東西也描述較多,因此對於重複的東西就不再贅述了。

在awk中,代碼塊{...}並不會分隔變量的作用域。例如在某個循環中我們已經使用了一個變量i,那麼退出循環以後,這個變量i仍然有效。

# awk 'BEGIN{for(i=1;i<=10;i++){} print i}'
11

if語句

# 單分支
if(cond){
    statements
}

# 雙分支
if(cond){
    statements1
}else{
    statements2
}

# 多分支
if(cond1){
    statements1
}else if(cond2){
    statements2
}else if(cond3){
    statements3
}else{
    statementsLast
}

我們來看一個有趣的示例。有一對夫妻,丈夫是一名程序員,妻子對丈夫說“出去買一個包子,如果看到賣西瓜的,就買兩個。”

# 自然語言理解
買1個包子
if(看到賣西瓜的){
    買2個西瓜
}

# 編程語言理解
if(看到賣西瓜的){
    買2個包子
}else{
    買1個包子
}

示例。

# cat if.awk 
BEGIN{
    if(mark>=0&&mark<60) {
        print "bad"
    } else if(mark>=60&&mark<90) {
        print "ordinary"
    } else if(mark>=90&&mark<=100) {
        print "Good"
    } else {
        print "error mark"
    }
}
# awk -v mark=100 -f if.awk 
Good

條件表達式

條件表達式(Conditional Expression),也就是我們常見的三目運算符。

selector ? if-true-exp : if-false-exp

三者均是表達式,首先計算selector,若值爲真(我們已經在講解布爾值時解釋過awk的真和假的概念了)則計算表達式if-true-exp,並將其返回值作爲整個表達式的返回值,否則計算if-false-exp並將其值作爲整個表達式的返回值。

來看兩個成功的示例。

awk 'BEGIN{mark=60;grade=mark>=60?"pass":"fail";print grade}'
awk 'BEGIN{mark=60;mark>=60?grade="pass":grade="fail";print grade}'

要注意這三者均是表達式,它們和普通語句的不同點在於它們可以計算評估並擁有返回值,而普通的語句是不行的。例如。

# awk 'BEGIN{mark=60;mark>=60?print "pass":print "fail"}'
awk: cmd. line:1: BEGIN{mark=60;mark>=60?print "pass":print "fail"}
awk: cmd. line:1:                        ^ syntax error

switch語句

awk的switch語句的功能和bash中的是一樣的。區別在於awk的switch語句的每個分支需要顯式使用break纔可離開分支,否則會發生分支的穿透。

switch(expression) {
    case val1|regex1 : statements1
    case val2|regex2 : statements2
    case val3|regex3 : statements3
... ...
    [default: statemtsLast]
}

示例如下。除了每個分支的break以外,其餘和我們在bash中所見到的switch並無二致。

# cat switch1.awk 
{
    switch($0){
        case 1:
            print "Monday."
            break
        case 2:
            print "Tuesday."
            break
        case 3:
            print "Wednesday."
            break
        case 4:
            print "Thursday."
            break
        case 5:
            print "Friday."
            break
        case 6:
            print "Saturday."
            break
        case 7:
            print "Sunday."
            break
        default:
            print "What day is today?"
    }
}
# awk -f switch1.awk 
0
What day is today?
8
What day is today?
1
Monday.
2
Tuesday.

What day is today?    # Ctrl+d結束輸入

我們可以註釋掉case=1|2|3|4分支的break指令來查看效果就可以理解什麼是分支穿透,這個很簡單就不演示了。

如果我們期望根據用戶的輸入來輸出工作日或者週末信息,可以註釋掉break。

{
    switch($0){
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            print "Weekday."
            break
        case 6:
        case 7:
            print "Weekend."
            break
        default:
            print "What day of today?"
            break
     }
}

case的值不支持邏輯或,會報錯。

# cat switch4.awk
{
    switch($0){
        case 1|2|3|4|5:
            print "Weekday."
            break
        case 6|7:
            print "Weekend."
            break
        default:
            print "What day of today?"
            break
     }
}
# awk -f switch4.awk 
awk: switch4.awk:3:         case 1|2|3|4|5:
awk: switch4.awk:3:               ^ syntax error

像這種情況就只能用正則了。

{
    switch($0){
        case /[12345]/:
            print "Weekday."
            break
        case /[67]/:
            print "Weekend."
            break
        default:
            print "What day is today?"
            break
    }
}

循環

while循環。

while(condition) {
    statements
}

do while循環。

do {
    statements
} while(condition)

for循環。

for(expr1;expr2;expr3) {
    statements
}

for循環遍歷數組。

for(idx in arrary) {
    statements
}

break和continue

awk中的break和continue與bash中的幾乎相同,區別僅在於awk中的break還用於switch...case...語句中的退出分支。

# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break}print i}}'
1
2
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue}print i}}'
1
2
4
5

next和nextfile

講到next就會和getline一同解釋。我們直接看命令執行結果。

# seq 1 5 | awk 'NR==3{next}{print}'
1
2
4
5
# seq 1 5 | awk 'NR==3{getline}{print}'
1
2
4
5

命令的執行結果相同,但是執行的過程是不同的。

代碼塊中的pattern和{action}一同構成一條規則(rule),在這個示例中有2條規則,第一條規則是pattern爲NR==3的,第二條規則則沒有pattern,意味着每一條記錄都符合該規則。

next:讀取下一行,然後重新回到規則的頭部(NR==3的位置)處理。

getline:讀取下一行,然後在當前位置(getline的位置)繼續往下處理。

也就是說第一條命令next之後重新回到規則頭部判斷NR是否等於3了,而第二條命令在getline之後執行的是無pattern的print。

nextfile:next命令是停止當前正在處理的記錄而後進入下一條記錄,而nextfile命令則是停止當前正在處理的文件而後進入下一個文件的處理。

# awk 'NR==3{nextfile}{print}' a.txt a.txt 
ID  name    gender  age  email          phone
1   Bob     male    28   [email protected]     18023394012
ID  name    gender  age  email          phone
... ...
10  Bruce   female  27   bcbd@139.com   13942943905
# awk 'FNR==3{nextfile}{print}' a.txt a.txt 
ID  name    gender  age  email          phone
1   Bob     male    28   [email protected]     18023394012
ID  name    gender  age  email          phone
1   Bob     male    28   [email protected]     18023394012

exit

exit [code]

exit用於退出awk程序,可以帶返回值。

如果在BEGIN或者main代碼塊中執行exit,則會停止當前處理而後執行END代碼塊(若有的話)的內容。也就是說exit命令的執行包含了執行END代碼塊。

如果在END代碼快中執行exit,則程序直接退出。

我們在講解BEGIN和END代碼塊時曾說過,若存在END代碼塊但是沒有遇到EOF(遇到文件的結尾或者STDIN中鍵入Ctrl+d),則END代碼塊阻塞不執行。但是現在有exit的執行,即使沒有EOF我們也可以執行END代碼塊的內容了。

# awk 'BEGIN{print "Hello world!";exit}END{print "This is not end!!!"}'    # 這裏既沒有文件的處理,下面2行也不是STDIN。
Hello world!
This is not end!!!

如果沒有BEGIN,則至少需要在main中做一次輸入。

# awk '{print "Hello world!";exit}END{print "This is not end!!!"}'
1    # 用戶輸入
Hello world!
This is not end!!!

有時候爲了讓BEGIN或者main中的exit真的像其他編程語言(如bash)那樣直接退出程序,我們可以在exit前設置一個變量(如flag等),然後在END的頭部判斷該變量來決定是否退出。

# awk 'BEGIN{print "Hello world!";flag=1;exit}{}END{if(flag){exit};print "end code"}'
Hello world!

較完整的僞代碼如下:

BEGIN {
    ... ...
    if(begin cond) {
        flag=1
        exit
    }
    ...
}
{
    ... ...
    if(main cond) {
        flag=1
        exit
    }
    ... ...
}
END {
    if(flag){exit}    # 必須在END頭部。
    ... ...
}

exit可以指定退出狀態碼(返回值),如果帶狀態碼的exit只有一次,那麼採用該狀態碼。

# awk 'BEGIN{exit 100}'
# echo $?
100
# awk '{exit 100}'    # 這裏必須至少一次輸入,如果直接Ctrl+d,返回的是0而不是exit的狀態碼。
1
# echo $?
100
# awk '{exit 100}'    # 直接Ctrl+d的結果。
# echo $?
0
# awk 'END{exit 100}'    # 這裏也是直接Ctrl+d,結果不同是因爲代碼塊不同。
# echo $?
100

如果有多個exit並且不止一個exit有狀態碼的話,則退出狀態碼是最後一次有執行的的帶狀態碼的exit的狀態碼。

# awk 'BEGIN{exit 10}{exit 20}END{exit 30}'
# echo $?
30
# awk '{exit 20}END{exit 30}'
# echo $?
30
# awk '{exit 20}END{exit 30}'
1
# echo $?
30
# awk 'BEGIN{exit 10}{exit 20}END{exit}'
# echo $?
10    # 最後的exit沒有狀態碼,最後一次有狀態碼的exit是BEGIN中的,main中的不會執行。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章