小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

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

 路人甲Java 2022-05-14

語法

變量

我覺得awk應該算是屬于動態(tài)編程語言。其變量不需要事先聲明才可以使用。我們想要使用的時候隨時引用即可,不需要事先聲明其數(shù)據(jù)類型。

awk的變量具有三種狀態(tài)。

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

引用未賦值的變量,其初始值為空字符串或者數(shù)字0。

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

# 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

對未引用且未賦值的數(shù)組進行判斷,返回untyped。但是對未引用的數(shù)組中的未引用的元素進行判斷卻會返回unassigned,這個比較奇怪,需要記住。

# awk 'BEGIN{print typeof(arr)}'
untyped
# awk 'BEGIN{print typeof(arr["name"])}'
unassigned

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

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ù)的變量賦值。

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
# 空

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

變量使用

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

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

不同位置賦值的變量的作用域會有所不同。例如在main代碼塊中賦值的變量在BEGIN中就無法引用,這點根據(jù)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
...

數(shù)據(jù)類型

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

數(shù)據(jù)是什么類型不能只看字面意義,例如看到數(shù)字不代表它就是數(shù)值類型。而是要看數(shù)據(jù)所處的上下文:在字符串操作環(huán)境下轉換成字符串類型,在數(shù)值操作環(huán)境下轉換為數(shù)值類型。

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

隱式轉換

1、對數(shù)據(jù)進行算術運算可以將其轉換為數(shù)值類型。

對于可轉換成數(shù)值的數(shù)據(jù)會正確轉換成數(shù)值,例如:“123”、“123abc”和“   123abc”。對于無法正確轉換成數(shù)值的數(shù)據(jù)則轉換成數(shù)值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比較常用,在不改變數(shù)值大小的情況下轉換成數(shù)值。

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

使用空格和雙引號連接。

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

變量a和b是數(shù)值,使用空格連接后隱式轉換成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、使用函數(shù)sprintf()基于預定義變量CONVFMT將數(shù)值轉換成字符串。

CONVFMT的默認值是%.6g。

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

變量a是小數(shù),遇到""字符串連接時基于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()函數(shù)顯式地將字符串轉換成數(shù)字。

# 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種變量的數(shù)據(jù)類型。

  • 字符串字面量。
  • 數(shù)值字面量。
  • 正則表達式字面量。

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

數(shù)值字面量

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

123
123.00
1.23e+8
1.23e-06

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

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

算術運算

以下算術運算符的優(yōu)先級由高至低。

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

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

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

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

當自增運算獨立作為語句存在時,二者沒有區(qū)別。

# 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

賦值操作符的優(yōu)先級是最低的,低于上述的算術運算符。

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

不建議書寫一些容易引起歧義的語句。因為不同的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}'

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

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

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

關于操作符的優(yōu)先級,在下文會有詳述。

正則表達式字面量

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

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

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

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

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

如果/pattern/單獨出現(xiàn),則等同于$0~/pattern/。

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

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

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

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

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

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

3、期望把正則字面量作為參數(shù)傳遞給函數(shù)。

a=/Alice/
func(arg1,a)

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

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

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

# awk 'BEGIN{a=@/Alice/;print typeof(a)}' a.txt
regexp
# awk 'BEGIN{a=@/Alice/}$0~a{print}' a.txt
2   Alice   female  24   def@gmail.com  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:]:數(shù)字。

[:alnum:]:字母或者數(shù)字。

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

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

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

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

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

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

[:cntrl:]:控制字符。

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

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

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

\<:單詞的左邊界。

\>:單詞的右邊界。

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

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

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

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

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

\':絕對行尾。

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

# awk '$2~/alice/{print}' a.txt 
# awk 'tolower($2)~/alice/{print}' a.txt 
2   Alice   female  24   def@gmail.com  18084925203
# awk 'BEGIN{IGNORECASE=1}$2~/alice/{print}' a.txt 
2   Alice   female  24   def@gmail.com  18084925203

awk布爾值

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

  • 數(shù)值0表示布爾假。
  • 空字符串表示布爾假。
  • 其他情況均表示布爾真。注意,"0"表示布爾真,因為它是非空字符串而不是數(shù)值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"}}'    # 賦值操作有返回值,此處返回數(shù)值100,布爾真。
True
# awk 'BEGIN{if(a==100){print "True"}}'
# awk 'BEGIN{if("alongdidi"~/a+/){print "True"}}'    # 正則匹配成功返回1,布爾真。
True

awk中比較操作

strnum類型

awk中數(shù)據(jù)的來源:

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

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

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

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

雖然來源于管道的外部數(shù)據(jù)原本應該均被識別為string,但是由于某些看起來形似數(shù)值因此在某些情況下被識別為了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:數(shù)組成員測試。

比較規(guī)則。

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

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

我們輸出$0和$1以及它們對應的數(shù)據(jù)類型。雖然是外部數(shù)據(jù)理應均為string但是由于形似數(shù)值,因此均被識別為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類型數(shù)據(jù),差別只在一個空格字符,strnum和numeric進行比較按照numeric方式進行比較。因此$0和$1均按照數(shù)值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通過管道傳遞過來的科學計數(shù)表示法的數(shù)值1e2,并將其識別為strnum,然后進行strnum和strnum的比較,按照numeric進行比較,結果顯而易見。對于三目運算符“?:”不懂的,后續(xù)也有解釋。

# 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:邏輯取反(非),一元運算符。

可以使用這個“!”或者“!!”將數(shù)據(jù)轉換成布爾值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

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

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

例如,打印a.txt的第1行至第4行數(shù)據(jù)。我們可以使用以往使用的方式實現(xiàn)。

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

或者使用我們剛說的思路實現(xiàn)。

# 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   abc@qq.com     18023394012
2   Alice   female  24   def@gmail.com  18084925203
3   Tony    male    21   aaa@163.com    17048792503

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

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

運算符優(yōu)先級

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

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

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

對于優(yōu)先級相同的運算符,運算順序是從左往右,不過對于賦值和冪運算來說是從右往左運算。

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

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

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

流程控制語句

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

在awk中,代碼塊{...}并不會分隔變量的作用域。例如在某個循環(huán)中我們已經使用了一個變量i,那么退出循環(huán)以后,這個變量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
}

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

# 自然語言理解
買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中的是一樣的。區(qū)別在于awk的switch語句的每個分支需要顯式使用break才可離開分支,否則會發(fā)生分支的穿透。

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指令來查看效果就可以理解什么是分支穿透,這個很簡單就不演示了。

如果我們期望根據(jù)用戶的輸入來輸出工作日或者周末信息,可以注釋掉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
    }
}

循環(huán)

while循環(huán)。

while(condition) {
    statements
}

do while循環(huán)。

do {
    statements
} while(condition)

for循環(huán)。

for(expr1;expr2;expr3) {
    statements
}

for循環(huán)遍歷數(shù)組。

for(idx in arrary) {
    statements
}

break和continue

awk中的break和continue與bash中的幾乎相同,區(qū)別僅在于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一同解釋。我們直接看命令執(zhí)行結果。

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

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

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

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

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

也就是說第一條命令next之后重新回到規(guī)則頭部判斷NR是否等于3了,而第二條命令在getline之后執(zhí)行的是無pattern的print。

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

# awk 'NR==3{nextfile}{print}' a.txt a.txt 
ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     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   abc@qq.com     18023394012
ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012

exit

exit [code]

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

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

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

我們在講解BEGIN和END代碼塊時曾說過,若存在END代碼塊但是沒有遇到EOF(遇到文件的結尾或者STDIN中鍵入Ctrl+d),則END代碼塊阻塞不執(zhí)行。但是現(xiàn)在有exit的執(zhí)行,即使沒有EOF我們也可以執(zhí)行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可以指定退出狀態(tài)碼(返回值),如果帶狀態(tài)碼的exit只有一次,那么采用該狀態(tài)碼。

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

如果有多個exit并且不止一個exit有狀態(tài)碼的話,則退出狀態(tài)碼是最后一次有執(zhí)行的的帶狀態(tài)碼的exit的狀態(tài)碼。

# 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沒有狀態(tài)碼,最后一次有狀態(tài)碼的exit是BEGIN中的,main中的不會執(zhí)行。

 

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多