Sed&awk筆記之awk篇(轉)

Awk是什麼

Awk、sed與grep,俗稱Linux下的三劍客,它們之間有很多相似點,但是同樣也各有各的特色,相似的地方是它們都可以匹配文本,其中sed和awk還可以用於文本編輯,而grep則不具備這個功用。sed是一種非交互式且面向字符流的編輯器(a "non-interactive" stream-oriented editor),而awk則是一門模式匹配的編程語言,因爲它的主要功能是用於匹配文本並處理,同時它有一些編程語言纔有的語法,例如函數、分支循環語句、變量等等,當然比起我們常見的編程語言,Awk相對比較簡單。

使用Awk,我們可以做以下事情:

  • 將文本文件視爲由字段和記錄組成的文本數據庫;
  • 在操作文本數據庫的過程中能夠使用變量;
  • 能夠使用數學運算和字符串操作
  • 能夠使用常見的編程結構,例如條件分支與循環;
  • 能夠格式化輸出;
  • 能夠自定義函數;
  • 能夠在awk腳本中執行UNIX命令;
  • 能夠處理UNIX命令的輸出結果;

裝備以上功能,awk能夠做得事情非常多。但千里之行,始於足下,我們首先從最基本的命令行語法開始,一步一步得走入awk的編程世界。

命令行語法

同sed一樣,awk的命令行語法也有兩種形式:

awk [-F ERE] [-v assignment] ... program [argument ...]
awk [-F ERE] -f progfile ...  [-v assignment] ...[argument ...]

這裏的program類似sed中的script,因爲我們一直強調awk是一門編程語言,所以將awk的腳本視爲一段代碼。而awk的腳本同樣可以寫到一個文件中,並通過-f參數指定,這一點和sed是一樣的。program一般多個pattern和action序列組成,當讀入的記錄匹配pattern時,纔會執行相應的action命令。這裏有一點要注意,在第一種形式中,除去命令行選項外,program參數一定要位於第一個位置。

Awk的輸入被解析成多個記錄(Record),默認情況下,記錄的分隔符是\n,因此可以認爲一行就是一個記錄,記錄的分隔符可以通過內置變量RS更改。當記錄匹配某個pattern時,纔會執行後續的action命令。

而每個記錄由進一步地被分隔成多個字段(Field),默認情況下字段的分隔符是空白符,例如空格、製表符等等,也可以通過-F ERE選項或者內置變量FS更改。在awk中,可以通過$1,$2...來訪問對應位置的字段,同時$0存放整個記錄,這一點有點類似shell下的命令行位置參數。關於這些內容,我們會在下面詳細介紹,這裏你只要知道有這些東西就好。

標準的awk命令行參數主要由以下三個:

  • -F ERE:定義字段分隔符,該選項的值可以是擴展的正則表達式(ERE);
  • -f progfile:指定awk腳本,可以同時指定多個腳本,它們會按照在命令行中出現的順序連接在一起;
  • -v assignment:定義awk變量,形式同awk中的變量賦值,即name=value,賦值發生在awk處理文本之前;

爲了便於理解,這裏舉幾個簡單的例子。通過-F參數設置冒號:爲分隔符,並打印各個字段:

[kodango@devops ~]$ echo "1:2:3" | awk -F: '{print $1 " and " $2 " and " $3}'
1 and 2 and 3

在awk的腳本中訪問通過-v選項設置的變量:

[kodango@devops ~]$ echo | awk -v a=1 'BEGIN {print a}'
1

從上面可以看到,通過-v選項設置的變量在BEGIN的位置就可以訪問了。BEGIN是一個特殊的pattern,它在awk處理輸入之前就會執行,可以認爲是一個初始化語句,與此對應的還有END

好像還沒介紹如何指定處理的文件,是不是最後的argument就是指定的文件?在看我這本書之前,我也是這樣認爲的,但是實際上arguemnt有兩種形式,它們分別是輸入文件(file)和變量賦值(assignment)。

awk可以同時指定多個輸入文件,如果輸入文件的文件名爲'-',表示從標準輸入讀取內容。

變量賦值類似-v選項,它的形式爲name=value。awk中的變量名同一般的編程語言無太多區別,但是不能同awk的保留關鍵字重名,可以查看awk的man手冊查詢哪些是保留關鍵字。而變量值只有兩種形式:字符串和數值。變量賦值必須位於腳本參數的後面,與文件名參數無先後順序的要求,但是位於不同位置的賦值它的執行時機是不同的。

我們用實際的例子來解釋這個區別,假設有兩個文件:a和b,它們的內容分別如下所示:

[kodango@devops awk_temp]$ cat a
file a
[kodango@devops awk_temp]$ cat b
file b

爲了說明賦值操作發生的時機,我們在BEGIN,正常處理,END三個地方都打印變量的值。

第一種情況: 變量賦值位於所有文件名參數之前

[kodango@devops awk_temp]$ awk 'BEGIN {print "BEGIN: " var} {print "PROCESS: " var} \
END {print "END: " var }' var=1 a
BEGIN: 
PROCESS: 1
END: 1

結果:賦值操作發生在正常處理之前,BEGIN動作之後。

第二種情況:變量賦值位於所有文件名之後:

[kodango@devops awk_temp]$ awk 'BEGIN {print "BEGIN: " var} {print "PROCESS: " var} \
END {print "END: " var }' a var=1  
BEGIN: 
PROCESS: 
END: 1

結果:賦值操作發生在正常處理之後,END動作之前。

第三種情況:變量賦值位於文件名之間:

[kodango@devops awk_temp]$ awk 'BEGIN {print "BEGIN: " var} {print "PROCESS: " var} \
END {print "END: " var }' a var=1 b
BEGIN: 
PROCESS: 
PROCESS: 1
END: 1

結果:賦值操作發生在處理前面的文件之後,並且位於處理後面的文件之前;

總結如下:

  1. 如果變量賦值在第一個文件參數之前,在BEGIN動作之後執行,影響到正常處理和END動作;
  2. 如果變量賦值在最後一個文件參數之後,在END動作之前執行,僅影響END動作;
  3. 如果文件參數不存在,情況同1所述;
  4. 如果變量賦值位於多個文件參數之間,在變量賦值前面的文件被處理後執行,影響到後續文件的處理和END動作;

所以變量賦值一定要考慮清楚用途,否則比較容易出錯,不過一般情況下也不會用到變量賦值。

自然地大家會將變量賦值與-v assignment選項進行比較,賦值的形式是一致的,但是-v選項的執行時機比變量賦值要早:

[kodango@devops awk_temp]$ echo 1 | awk -v var=a 'BEGIN {print "BEGIN: " var}'
BEGIN: a

可見,-v選項的賦值操作在BEGIN動作之前就執行了。

變量賦值一定要小心不要與保留關鍵字重名,否則會報錯:

[kodango@devops awk_temp]$ echo 1 | awk -v BEGIN=1 'BEGIN {print "BEGIN: " BEGIN}'
awk: fatal: cannot use gawk builtin `BEGIN' as variable name

 

記錄(Record)與字段(Field)

 

對於數據庫來說,一個數據庫表是由多條記錄組成的,每一行表示一條記錄(Record)。每條記錄由多列組成,每一列表示一個字段(Field)。Awk將一個文本文件視爲一個文本數據庫,因此它也有記錄和字段的概念。默認情況下,記錄的分隔符是回車,字段的分隔符是空白符,所以文本文件的每一行表示一個記錄,而每一行中的內容被空白分隔成多個字段。利用字段和記錄,awk就可以非常靈活地處理文件的內容。

可以通過-F選項來修改默認的字段分隔符,例如/etc/passwd的每一行都是由冒號分隔成多個字段的,所以這裏就需要將分隔符設置成冒號:

[kodango@devops awk_temp]$ awk -F: '{print $1}' /etc/passwd | head -3
root
bin
daemon

這裏通過$1引用第一人字段,類似地$2表示第二個字段,$3表示第三個字段.... $0則表示整個記錄。內置變量NF記錄着字段的個數,所以$NF表示最後一個字段:

[kodango@devops awk_temp]$ awk -F: '{print $NF}' /etc/passwd | head -3
/bin/bash
/bin/false
/bin/false

當然,$(NF-1)表示倒數第二個。

內置變量FS也可以用於更改字段分隔符,它記錄着當前的字段分隔符:

[kodango@devops awk_temp]$ awk -F: '{print FS}' /etc/passwd | head -1
:
[kodango@devops awk_temp]$ awk -v FS=: '{print $1}' /etc/passwd | head -1
root

記錄的分隔符可以通過內置變量RS更改:

[kodango@devops awk_temp]$ awk -v RS=: '{print $0}' /etc/passwd | head -1
root

如果將RS設置成空,行爲有就一點怪異了,它會將連續不爲空行的所有行(一個段落)當作一個記錄,而且強制回車爲字段分隔符:

[kodango@devops awk_temp]$ cat awk_man.txt 

The awk utility shall execute programs written in the awk programming language,
which is specialized for textual data manipulation. An awk program is a sequence
of patterns and corresponding actions.  When  input  is  read  that matches a
pattern, the action associated with that pattern is carried out.

Input shall be interpreted as a sequence of records. By default, a record is a line,
less its terminating <newline>, but this can be changed by using the RS built-in
variable. Each record of input shall be matched in turn against each pattern in the
program. For each pattern matched, the associated action shall be executed.

[kodango@devops awk_temp]$ awk 'BEGIN {RS="";FS=":"} {print "First line: " $1}' awk_man.txt 
First line: The awk utility shall execute programs written in the awk programming language,
First line: Input shall be interpreted as a sequence of records. By default, a record is a line,

這裏,我們將變量賦值放到BEGIN動作中執行,因爲BEGIN動作是在文件處理之前執行的,專門用於放初始化的語句。FS的賦值在這裏是無效的,awk依然使用回車符來分隔字段。

腳本(Script)組成

命令行中的program部分,可以稱爲awk代碼,也可以稱爲awk腳本。一段awk腳本是由多個'pattern { action }'序列組成的。action是一個或者多個語句,它在輸入行匹配pattern的時候被執行。如果pattern爲空,表明這個action會在每一行處理時都會被執行。下面的例子簡單地打印文件的每一行,這裏不帶任何參數的print語句打印的是整個記錄,類似'print $0':

[kodango@devops awk_temp]$ echo -e 'line1\nline2' | awk '{print}' 
line1
line2

除了pattern { action },還可以在腳本中定義自定義的函數,函數定義格式如下所示:

function name(parameter list) { statements }

函數的參數列表用逗號分隔,參數默認是局部變量,無法在函數之外訪問,而在函數中定義的變量爲全局變量,可以在函數之外訪問,如:

[kodango@devops awk_temp]$ echo line1 | awk '
function t(a) {
    b=a;
    print a;
} 

{
    print b;
    t("kodango.me"); 
    print b;
}'

kodango.me
kodango.me

Awk腳本中的語句使用空行或者分號分隔,使用分號可以放在同一行,不過有時候會影響可讀性,尤其是分支或循環結構中,很容易出錯。

如果Awk中的一個語句太長,要分成多行,可以在行爲使用反斜槓'\':

[kodango@devops awk_temp]$ cat test.awk 

function t(a)
{
    b=a
    print "This is a very long line, so use backslash to escape the newline \
then we will print the variable a: a=" a
} 

{ print b; t("kodango.me"); print b;}
[kodango@devops awk_temp]$ echo 1 | awk -f test.awk 

This is a very long line, so use backslash to escape the newline then we will print the variable a: a=kodango.me
kodango.me

這裏我們將腳本寫到文件中,並通過-f參數來指定。但是,在一些特殊符號之後,是可以直接換行的,例如", { && ||"。

模式(Pattern)

模式是awk中比較重要的一部分,它有以下幾種情況:

  • /regular expression/: 擴展的正則表達式(Extended Regular Expression), 關於ERE可以參考這篇文章
  • relational expression: 關係表達式,例如大於、小於、等於,關係表達式結果爲true表示匹配;
  • BEGIN: 特殊的模式,在第一個記錄處理之前被執行,常用於初始化語句的執行;
  • END: 特殊的模式,在最後一個記錄處理之前被執行,常用於輸出彙總信息;
  • pattern, pattern:模式對,匹配兩者之間的所有記錄,類似sed的地址對;

例如查找匹配數字3的行:

[kodango@devops awk_temp]$ seq 1 20 | awk '/3/ {print}'
3
13

相反地,可以在在正則表達式之前加上'!'表示不匹配:

[kodango@devops awk_temp]$ seq 1 5 | awk '!/3/ {print}'
1
2
4
5

除了BEGINEND這兩個特殊的模式外,其餘的模式都可以使用'&&'或者'||'運算符組合,前者表示邏輯與,後者表示邏輯或:

[kodango@devops awk_temp]$ seq 1 50 | awk '/3/ && /1/ {print}'
13
31

前面的正則都是整行匹配,有時候僅僅需要匹配某個字符,這樣我們可以用表達式$n ~ /ere/

[kodango@devops ~]$ awk '$1 ~ /ko/ {print}' /etc/passwd
kodango:x:1000:1000::/home/kodango:/bin/bash

有時候我們只想顯示特定和行,例如顯示第一行:

[kodango@devops ~]$ seq 1 5 | awk 'NR==1 {print}'
1

正則表達式(Regular Expression)

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

表達式(Expressions)

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

Awk中的變量有三種類型:用戶定義的變量,內置變量和字段變量。其中,內置變量名都是大寫的。

變量並不非一定要被聲明或者被初始化,一個變量默認的值是空字符串,只是在某些上下文上會隱式的自動轉換成數字0(例如數學運算),記住awk中的變量是無類型的,不存在字符串變量還是數字變量的區別,只是有時候爲了解說方便,纔會這麼說。(感謝網友@紫雲妃提醒)

字段變量可以用$n來引用,n的取值範圍爲[0,NF]。n可以爲一個變量,例如$NF代碼最後一個字段,而$(NF-1)表示倒數第二個字段。

數組

數組是一種特殊的變量,awk中的數組都是關聯數組,它的下標都是字符串值(man手冊中的原話是:All arrays in AWK are associative, i.e. indexed by string values),即使你使用的下標是一個數字,awk也會將下標隱式轉換成字符串。所以容易給人一個誤解,數組的下標可以是數字或者字符串。

數組的賦值很簡單,下面將value賦值給數組下標爲index的元素:

array[index]=value

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

for (item in array)

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

if (item in array)

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

[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的用法:

[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,我們可以做到偷樑換柱:

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

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

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

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

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

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

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

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

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

[kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]="-"} {print}' a b
a
a    # --> 這裏按下CTRL+D停止輸入
file b

b. 刪除ARGV元素

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

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

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

c. 增加ARGV元素

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

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

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

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

2. CONVFMTOFMT

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

[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,我們可以定義自己的轉換格式:

[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的作用是類似的,只不過是影響輸出的時候數字轉換成字符串的格式:

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

3. ENVIRON

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

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

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

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

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

[kodango@devops awk_temp]$ awk 'BEGIN { 
for (env in ENVIRON) 
    printf "%s=%s\n", env, ENVIRON[env]; 
}'

4. RLENGTHRSTART

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

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

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

運算符

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

[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

...以下省略...

語句(Statement)

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

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

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

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

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

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

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

[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語句中,輸出的變量之間帶不帶逗號是有區別的:

[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重新定義:

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

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

print items > output-file
print items >> output-file
print items | command

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

[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兩個文件中:

[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

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

[kodango@devops awk_temp]$ cat num.list 
1
3
2
9
5

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

[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():返回0到1的一個隨機數值,不包含1;
  • srand([expr]):設置隨機種子,一般與rand函數配合使用,如果參數爲空,默認使用當前時間爲種子;

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

[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()函數來設置種子:

[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的整數:

[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。替換的動作會直接修改變量的值。

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

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

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

[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。

例如:

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

4. length[([s])]

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

例如:

[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。

例如:

[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分隔。函數返回值分隔的個數。

例如:

[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循環:

[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,只不過不會將格式化後的內容輸出到標準輸出,而是當作返回值返回。

例如:

[kodango@devops ~]$ awk 'BEGIN {
> var=sprintf("%s=%s", "name", "value")
> print var
> }'
name=value

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

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

例如:

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

9. tolower(s)

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

例如:

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

10. toupper(s)

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

例如

[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的輸入:

[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值也會被更新:

[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,並且這時候會更新
NFNRFNR

[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函數的參數與管道的命令一致:

[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

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

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

結束語

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

附Peteris Krumins在他博客上發佈的一份cheet sheetawk cheat sheet (.pdf)

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