《sed & awk》讀書筆記之 awk 篇(下)

正則表達式(Regular Expression)

和sed篇一樣,這裏我不會詳細介紹正則表達式。因爲正則表達式的內容介紹起來太麻煩,還是推薦同學閱讀現有的文章(如Linux/Unix工具與正則表達式的POSIX規範),裏面對各個流派的正則表達式歸納地很清楚了。

表達式(Expressions)

表達式可以由常量、變量、運算符和函數組成,常數和變量的值可以爲字符串和數值。

Awk中的變量有三種類型:用戶定義的變量,內置變量和字段變量。其中,內置變量名都是大寫的。變量並不非一定要被聲明或者被初始化,未初始化的字符串變量的值爲”",未初始化的數值變量的值爲0。字段變量可以用$n來引用,n的取值範圍爲[0,NF]。n可以爲一個變量,例如$NF代碼最後一個字段,而$(NF-1)表示倒數第二個字段。

數組

數組是一種特殊的變量,在awk中,比較特殊地是,數組的下標可以爲數字或者字符串。數組的賦值很簡單,下面將value賦值給數組下標爲index的元素:

1

array[index]=value

可以用for..in..語法遍歷數組元素,其中item是數組元素對應的下標:

1

for   (item in array)

當然也可以在if分支判斷中使用in操作符:

1

if   (item in array)

一個完整的例子如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

[kodango@devops ~]$ echo "1 2 3" | awk '{

for   (i=0;i<NF;i++)

  a[i]=i;

}

END {

print 3 in a

for   (i in a)

   printf "%s: %s\n", i, a[i];

}'

0

0: 0

1: 1

2: 2

內置變量

Awk在內部維護了許多內置變量,或者稱爲系統變量,例如之前提到的FSRS等等。常見的內置變量如下表所示

變量名

描述

ARGC

命令行參數的各個,即ARGV數組的長度

ARGV

存放命令行參數

CONVFMT

定義awk內部數值轉換成字符串的格式,默認值爲”%.6g”

OFMT

定義輸出時數值轉換成字符串的格式,默認值爲”%.6g”

ENVIRON

存放系統環境變量的關聯數組

FILENAME

當前被處理的文件名

NR

記錄的總個數

FNR

當前文件中的記錄的總個數

FS

字段分隔符,默認爲空白

NF

每個記錄中字段的個數

RS

記錄的分隔符,默認爲回車

OFS

輸出時字段的分隔符,默認爲空白

ORS

輸出時記錄的分隔符,默認爲回車

RLENGTH

被match函數匹配的子串長度

RSTART

被match函數匹配的子串位於目標字符串的起始下標

下面主要介紹幾個比較難理解的內置變量:

1.ARGVARGC

ARGVARGC的意思比較好理解,就像C語言main(int argc, char **argv)ARGV數組的下標從0開始到ARGC-1,它存放的是命令行參數,並且排除命令行選項(例如-v/-f)以及program部分。因此事實上ARGV只是存儲argument的部分,即文件名(file)以及命令行變量賦值兩部分的內容。

通過下面的例子可以大概瞭解ARGC與ARGV的用法:

1

2

3

4

5

6

7

[kodango@devops awk_temp]$  awk 'BEGIN {

>     for   (i = 0; i < ARGC; i++)

>           print ARGV[i]

>    }' inventory-shipped BBS-list

awk

inventory-shipped

BBS-list

ARGV的用法不僅限於此,它是可以修改的,可以更改數組元素的值,可以增加數組元素或者刪除數組元素。

a. 更改ARGV元素的值

假設我們有a, b兩個文件,它們各有一行內容:file a和file b。現在利用ARGV,我們可以做到偷樑換柱:

1

2

[kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]="b"} {print}' a

file b

這裏要注意ARGV[1]="b"的引號不能缺少,否則ARGV[1]=b會將變量b的值賦值給ARGV[1]

當awk處理完一個文件之後,它會從ARGV的下一個元素獲取參數,如果是一個文件則繼續處理,如果是一個變量賦值則執行賦值操作:

1

2

[kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]="var=1"} {print var}' a b

1

爲什麼這裏只打印一次變量值呢?可以回頭再看看上一篇中介紹變量賦值的內容。

而當下一個元素爲空時,則跳過不處理,這樣可以避開處理某個文件:

1

2

[kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]=""} {print}' a b

file b

上面的例子中a這個文件就被跳過了。

而當下一個元素的值爲”-”時,表明從標準輸入讀取內容:

1

2

3

4

[kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]="-"} {print}' a b

a

a    #   --> 這裏按下CTRL+D停止輸入

file b

b. 刪除ARGV元素

刪除ARGV元素和將元素的值賦值爲空的效果是一樣的,它們都會跳轉對某個參數的處理:

1

2

[kodango@devops awk_temp]$ awk 'BEGIN{delete ARGV[1]} {print}' a b

file b

刪除數組元素可以用delete語句。

c. 增加ARGV元素

我第一次看到ARGV變量的時候就在想,能不能利用ARGV變量避免提供命令行參數,就像這樣:

1

awk 'BEGIN{ARGV[1]="a";} {print}'

但是事實上這樣不行,awk會依然從標準輸入中獲取內容。下面的方法倒是可以,首先增加ARGC的值,再增加ARGV元素,我到現在也沒搞懂這兩者的區別:

1

2

[kodango@devops awk_temp]$ awk 'BEGIN{ARGC+=1;ARGV[1]="a"} {print}'

file a

2.CONVFMTOFMT

Awk中允許數值到字符串相互轉換,其中內置變量CONVFMT定義了awk內部數值到字符串轉換的格式,它的默認值爲”%.6g”:

1

2

3

4

[kodango@devops awk_temp]$ awk 'BEGIN {

    printf "CONVFMT=%s,   num=%f, str=%s\n", CONVFMT, 12.11,   12.11

}'  

CONVFMT=%.6g,   num=12.110000, str=12.11

通過更改CONVFMT,我們可以定義自己的轉換格式:

1

2

3

4

5

[kodango@devops awk_temp]$ awk 'BEGIN {

    CONVFMT="%d";

    printf "CONVFMT=%s,   num=%f, str=%s\n", CONVFMT, 12.11,   12.11

}' 

CONVFMT=%d,   num=12.110000, str=12

與此對應地還有一個內置變量OFMT,它與CONVFMT的作用是類似的,只不過是影響輸出的時候數字轉換成字符串的格式:

1

2

[kodango@devops awk_temp]$ awk 'BEGIN { OFMT="%d";print 12.11 }' 

12

3.ENVIRON

ENVIRON是一個存放系統環境變量的關聯數組,它的下標是環境變量名稱,值是相應環境變量的值。例如:

1

2

[kodango@devops awk_temp]$ awk 'BEGIN { print ENVIRON["USER"] }' 

kodango

利用環境變量也可以將值傳遞給awk:

1

2

[kodango@devops awk_temp]$ U=hello awk 'BEGIN { print ENVIRON["U"] }' 

hello

可以利用for..in循環遍歷ENVIRON數組:

1

2

3

4

[kodango@devops awk_temp]$ awk 'BEGIN {

for   (env in   ENVIRON)

    printf "%s=%s\n", env, ENVIRON[env];

}'

4.RLENGTHRSTART

RLENGTHRSTART都是與match函數相關的,前者表示匹配的子串長度,後者表示匹配的子串位於目標字符串的起始下標。例如:

1

2

[kodango@devops ~]$ awk 'BEGIN   {match("hello,world", /llo/); print RSTART,RLENGTH}'

3 3

關於match函數,我們會在以後介紹。

運算符

表達式中必然少不了運算符,awk支持的運算符可以參見man手冊中的“Expressions in awk”一小節內容:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

[kodango@devops awk_temp]$ man awk | grep "^ *Table: Expressions in" -A 42 | sed 's/^ *//'

                                       Table:   Expressions in Decreasing Precedence in awk

Syntax                  Name                        Type of Result   Associativity

( expr   )                Grouping                    Type of expr     N/A

$expr                   Field reference             String           N/A

++   lvalue               Pre-increment               Numeric          N/A

--   lvalue               Pre-decrement               Numeric          N/A

lvalue   ++               Post-increment              Numeric          N/A

lvalue   --               Post-decrement              Numeric          N/A

expr ^ expr             Exponentiation              Numeric          Right

! expr                  Logical   not                 Numeric          N/A

+ expr                  Unary   plus                  Numeric          N/A

- expr                  Unary   minus                 Numeric          N/A

expr * expr             Multiplication              Numeric          Left

...以下省略...

1

語句(Statement)

到目前爲止,用得比較多的語句就是print,其它的還有printf、delete、break、continue、exit、next等等。這些語句與函數不同的是,它們不會使用帶括號的參數,並且沒有返回值。不過也有意外,比如printf就可以像函數一樣的調用:

1

2

[kodango@devops awk_temp]$ echo 1 | awk '{printf("%s\n", "abc")}'

abc

breakcontinue語句,大家應該比較瞭解,分別用於跳出循環和跳到下一個循環。

delete用於刪除數組中的某個元素,這個我們在上面介紹ARGV的時候也使用過。

exit的用法顧名思義,就是退出awk的處理,然後會執行END部分的內容:

1

2

3

[kodango@devops awk_temp]$ echo $'line1\nline2' | awk '{print;exit} END {print "exit.."}'

line1

exit..

next語句類似sed的n命令,它會讀取下一條記錄,並重新回到腳本的最開始處執行:

1

2

3

4

5

6

7

8

9

10

[kodango@devops awk_temp]$ echo $'line1\nline2' | awk '{

> print "Before   next.."

>   print $0

>   next

> print "After   next.."

>   }'

Before   next..

line1

Before   next..

line2

從上面可以看出next後面的print語句不會執行。

print與printf語句是使用最多的,它們將內容輸出到標準輸出。注意在print語句中,輸出的變量之間帶不帶逗號是有區別的:

1

2

3

4

[kodango@devops awk_temp]$ echo "1 2" | awk '{print $1, $2}'

1 2

[kodango@devops awk_temp]$ echo "1 2" | awk '{print $1 $2}'

12

print輸出時,字段之間的分隔符可以由OFS重新定義:

1

2

[kodango@devops awk_temp]$ echo "1 2" | awk '{OFS=";";print $1,$2}'

1;2

除此之外,print的輸出還可以重定向到某個文件中或者某個命令:

1

2

3

print items > output-file

print items >> output-file

print items | command

假設有這一樣一個文件,第一列是語句名稱,第二列是對應的說明:

1

2

3

4

5

[kodango@devops awk_temp]$ cat column.txt

statement|description

delete|delete   item from an array

exit|exit from the awk process

next|read next input record and process

現在我們要將兩列的內容分別輸出到statement.txt和description.txt兩個文件中:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

[kodango@devops awk_temp]$ awk -F'|' '{

> print $1 > "statement.txt";

> print $2 > "description.txt"

>   }' column.txt

[kodango@devops awk_temp]$ cat statement.txt

statement

delete

exit

next

[kodango@devops awk_temp]$ cat description.txt

description

delete   item from an array

exit from the awk process

read next input   record and process

下面是一個重定向到命令的例子,假設我們要對下面的文件進行排序:

1

2

3

4

5

6

[kodango@devops awk_temp]$ cat num.list

1

3

2

9

5

可以通過將print的內容重定向到”sort -n”命令:

1

2

3

4

5

6

[kodango@devops awk_temp]$ awk '{print | "sort -n"}' num.list

1

2

3

5

9

printf命令的用法與print類似,也可以重定向到文件或者輸出,只不過printf比print多了格式化字符串的功能。printf的語法也大多數語言包括bash的printf命令類似,這裏就不多介紹了。

awk的函數分成數學函數、字符串函數、I/O處理函數以及用戶自定義的函數,其中用戶自定義的函數我們在上一篇中也有簡單的介紹,下面我們一一來介紹這幾類函數。

數學函數

awk中支持以下數學函數:

atan2(y,x):反正切函數;

cos(x):餘弦函數;

sin(x):正弦函數;

exp(x):以自然對數e爲底指數函數;

log(x):計算以e 爲底的對數值;

sqrt(x):絕對值函數;

int(x):將數值轉換成整數;

rand():返回01的一個隨機數值,不包含1

srand([expr]):設置隨機種子,一般與rand函數配合使用,如果參數爲空,默認使用當前時間爲種子;

例如,我們使用rand()函數生成一個隨機數值:

1

2

3

4

[kodango@devops awk_temp]$ awk 'BEGIN {print rand(),rand();}'

0.237788   0.291066

[kodango@devops awk_temp]$ awk 'BEGIN {print rand(),rand();}'

0.237788   0.291066

但是你會發現,每次awk執行都會生成同樣的隨機數,但是在一次執行過程中產生的隨機數又是不同的。因爲每次awk執行都使用了同樣的種子,所以我們可以用srand()函數來設置種子:

1

2

3

4

[kodango@devops awk_temp]$ awk 'BEGIN {srand();print rand(),rand();}'

0.171625   0.00692412

[kodango@devops awk_temp]$ awk 'BEGIN {srand();print rand(),rand();}'

0.43269   0.782984

這樣每次生成的隨機數就不一樣了。

利用rand()函數我們也可以生成1到n的整數:

1

2

3

4

5

[kodango@devops awk_temp]$ awk '

> function randint(n) { return int(n*rand()); }

>   BEGIN { srand(); print randint(10);

>   }'

3

字符串函數

awk中包含大多數常見的字符串操作函數。

1.sub(ere, repl[, in])

描述:簡單地說,就是將in中匹配ere的部分替換成repl,返回值是替換的次數。如果in參數省略,默認使用$0。替換的動作會直接修改變量的值。

下面是一個簡單的替換的例子:

1

2

3

[kodango@devops ~]$ echo "hello,   world" | awk '{print sub(/ello/, "i"); print}'

1

hi,   world

在repl參數中&是一個元字符,它表示匹配的內容,例如:

1

2

[kodango@devops ~]$ awk 'BEGIN   {var="kodango"; sub(/kodango/, "hello, &", var);   print var}'

hello,   kodango

2.gsub(ere, repl[, in])

描述:同sub()函數功能類似,只不過是gsub()是全局替換,即替換所有匹配的內容。

3.index(s, t)

描述:返回字符串t在s中出現的位置,注意這裏位置是從1開始計算的,如果沒有找到則返回0。

例如:

1

2

3

4

[kodango@devops ~]$ awk 'BEGIN {print   index("kodango", "o")}'

2

[kodango@devops ~]$ awk 'BEGIN {print   index("kodango", "w")}'

0

4.length[([s])]

描述:返回字符串的長度,如果參數s沒有指定,則默認使用$0作爲參數。

例如:

1

2

3

4

[kodango@devops ~]$ awk 'BEGIN {print length('kodango');}'

0

[kodango@devops ~]$ echo "first line" | awk '{print length();}'

10

5.match(s, ere)

描述: 返回字符串s匹配ere的起始位置,如果不匹配則返回0。該函數會定義RSTARTRLENGTH兩個內置變量。RSTART與返回值相同,RLENGTH記錄匹配子串的長度,如果不匹配則爲-1。

例如:

1

2

3

4

5

6

[kodango@devops ~]$ awk 'BEGIN {

print match("kodango", /dango/);

printf "Matched at: %d, Matched substr length: %d\n", RSTART, RLENGTH;

}'

3

Matched   at: 3, Matched substr length: 5

6.split(s, a[, fs])

描述:將字符串按照分隔符fs,分隔成多個部分,並存到數組a中。注意,存放的位置是從第1個數組元素開始的。如果fs爲空,則默認使用FS分隔。函數返回值分隔的個數。

例如:

1

2

3

4

5

6

7

8

9

10

[kodango@devops ~]$ awk 'BEGIN {

> split("1;2;3;4;5", arr, ";")

> for (i in arr)

>     printf "arr[%d]=%d\n",   i, arr[i];

>   }'

arr[4]=4

arr[5]=5

arr[1]=1

arr[2]=2

arr[3]=3

這裏有一個奇怪的地方是for..in..輸出的數組不是按順序輸出的,如果要按順序輸出可以用常規的for循環:

1

2

3

4

5

6

7

8

9

10

11

12

13

[kodango@devops ~]$ awk 'BEGIN {

> split("1;2;3;4;5", arr, ";")

> for (i=0;^C

[kodango@devops ~]$ awk 'BEGIN {

> n=split("1;2;3;4;5", arr, ";")

> for (i=1; i<=n; i++)

>     printf "arr[%d]=%d\n",   i, arr[i];

>   }'

arr[1]=1

arr[2]=2

arr[3]=3

arr[4]=4

arr[5]=5

7.sprintf(fmt, expr, expr, ...)

描述:類似printf,只不過不會將格式化後的內容輸出到標準輸出,而是當作返回值返回。

例如:

1

2

3

4

5

[kodango@devops ~]$ awk 'BEGIN {

> var=sprintf("%s=%s", "name", "value")

>   print var

>   }'

name=value

8.substr(s, m[, n])

描述:返回從位置m開始的,長度爲n的子串,其中位置從1開始計算,如果未指定n或者n值大於剩餘的字符個數,則子串一直到字符串末尾爲止。

例如:

1

2

3

4

[kodango@devops ~]$ awk 'BEGIN { print   substr("kodango", 2, 3); }'

oda

[kodango@devops ~]$ awk 'BEGIN { print   substr("kodango", 2); }'

odango

9.tolower(s)

描述:將字符串轉換成小寫字符。

例如:

1

2

[kodango@devops ~]$ awk 'BEGIN {print   tolower("KODANGO");}'

kodango

10.toupper(s)

描述:將字符串轉換成大寫字符。

例如

1

2

[kodango@devops ~]$ awk 'BEGIN {print   tolower("kodango");}'

KODANGO

I/O處理函數

1.getline

getline的用法相對比較複雜,它有幾種不同的形式。不過它的主要作用就是從輸入中每次獲取一行輸入。

a.expression | getline [var]

這種形式將前面管道前命令輸出的結果作爲getline的輸入,每次讀取一行。如果後面跟有var,則將讀取的內容保存到var變量中,否則會重新設置$0和NF

例如,我們將上面的statement.txt文件的內容顯示作爲getline的輸入:

1

2

3

4

5

[kodango@devops awk_temp]$ awk 'BEGIN { while("cat statement.txt" | getline   var) print var}'

statement

delete

exit

next

上面的例子中命令要用雙引號,”cat statement.txt“,這一點同print/printf是一樣的。

如果不加var,則直接寫到$0中,注意NF值也會被更新:

1

2

3

4

5

[kodango@devops awk_temp]$ awk 'BEGIN { while("cat statement.txt" | getline)   print $0,NF}'

statement   1

delete   1

exit 1

next   1

b.getline [var]

第二種形式是直接使用getline,它會從處理的文件中讀取輸入。同樣地,如果var沒有,則會設置$0,並且這時候會更新NF,NRFNR

1

2

3

4

5

6

7

[kodango@devops awk_temp]$ awk   '{     

> while (getline)

>      print NF, NR, FNR, $0;

>   }' statement.txt

1 2 2   delete

1 3 3 exit

1 4 4   next

c.getline [var] < expression

第三種形式從expression中重定向輸入,與第一種方法類似,這裏就不加贅述了。

2.close

close函數可以用於關閉已經打開的文件或者管道,例如getline函數的第一種形式用到管道,我們可以用close函數把這個管道關閉,close函數的參數與管道的命令一致:

1

2

3

4

5

6

7

8

9

10

[kodango@devops awk_temp]$ awk 'BEGIN {

while("cat statement.txt" | getline) {

   print   $0;

   close("cat   statement.txt");

}}'

statement

statement

statement

statement

statement

但是每次讀了一行後,關閉管道,然後重新打開又重新讀取第一行就死循環了。所以要慎用,一般情況下也很少會用到close函數。

3.system

這個函數很簡單,就是用於執行外部命令,例如:

1

2

[kodango@devops awk_temp]$ awk 'BEGIN {system("uname -r");}'

3.6.2-1-ARCH

結束語

快速瞭解Awk系列的幾篇文章相對比較粗糙,我是參考Awk的man手冊以及《Sed & Awk》附錄B總結而成的,但是應該可以讓大家對awk有一個大致的瞭解,歡迎大家一起交流。

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