Sed&awk筆記之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的命令列語法也有兩種形式:
1 2 |
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引數設定冒號:為分隔符,並列印各個欄位:
1 2 |
[kodango@devops ~]$ echo "1:2:3" | awk -F: '{print $1 " and " $2 " and " $3}' 1 and 2 and 3 |
在awk的指令碼中訪問通過-v選項設定的變數:
1 2 |
[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,它們的內容分別如下所示:
1 2 3 4 |
[kodango@devops awk_temp]$ cat a file a [kodango@devops awk_temp]$ cat b file b |
為了說明賦值操作發生的時機,我們在BEGIN,正常處理,END三個地方都列印變數的值。
第一種情況: 變數賦值位於所有檔名引數之前
1 2 3 4 5 |
[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
動作之後。
第二種情況:變數賦值位於所有檔名之後:
1 2 3 4 5 |
[kodango@devops awk_temp]$ awk 'BEGIN {print "BEGIN: " var} {print "PROCESS: " var} \ END {print "END: " var }' a var=1 BEGIN: PROCESS: END: 1 |
結果:賦值操作發生在正常處理之後,END
動作之前。
第三種情況:變數賦值位於檔名之間:
1 2 3 4 5 6 |
[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選項的執行時機比變數賦值要早:
1 2 |
[kodango@devops awk_temp]$ echo 1 | awk -v var=a 'BEGIN {print "BEGIN: " var}' BEGIN: a |
可見,-v選項的賦值操作在BEGIN
動作之前就執行了。
變數賦值一定要小心不要與保留關鍵字重名,否則會報錯:
1 2 |
[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的每一行都是由冒號分隔成多個欄位的,所以這裡就需要將分隔符設定成冒號:
1 2 3 4 |
[kodango@devops awk_temp]$ awk -F: '{print $1}' /etc/passwd | head -3 root bin daemon |
這裡通過$1引用第一人欄位,類似地$2表示第二個欄位,$3表示第三個欄位…. $0則表示整個記錄。內建變數NF記錄著欄位的個數,所以$NF表示最後一個欄位:
1 2 3 4 |
[kodango@devops awk_temp]$ awk -F: '{print $NF}' /etc/passwd | head -3 /bin/bash /bin/false /bin/false |
當然,$(NF-1)表示倒數第二個。
內建變數FS也可以用於更改欄位分隔符,它記錄著當前的欄位分隔符:
1 2 3 4 |
[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更改:
1 2 |
[kodango@devops awk_temp]$ awk -v RS=: '{print $0}' /etc/passwd | head -1 root |
如果將RS設定成空,行為有就一點怪異了,它會將連續不為空行的所有行(一個段落)當作一個記錄,而且強制回車為欄位分隔符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[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
‘:
1 2 3 |
[kodango@devops awk_temp]$ echo -e 'line1\nline2' | awk '{print}' line1 line2 |
除了
,還可以在指令碼中定義自定義的函式,函式定義格式如下所示:pattern { action }
1 |
function name(parameter list) { statements } |
函式的引數列表用逗號分隔,引數預設是區域性變數,無法在函式之外訪問,而在函式中定義的變數為全域性變數,可以在函式之外訪問,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[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中的一個語句太長,要分成多行,可以在行為使用反斜槓’\’:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[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的行:
1 2 3 |
[kodango@devops awk_temp]$ seq 1 20 | awk '/3/ {print}' 3 13 |
相反地,可以在在正規表示式之前加上’!’表示不匹配:
1 2 3 4 5 |
[kodango@devops awk_temp]$ seq 1 5 | awk '!/3/ {print}' 1 2 4 5 |
除了BEGIN
和END
這兩個特殊的模式外,其餘的模式都可以使用’&&’或者’||’運算子組合,前者表示邏輯與,後者表示邏輯或:
1 2 3 |
[kodango@devops awk_temp]$ seq 1 50 | awk '/3/ && /1/ {print}' 13 31 |
前面的正則都是整行匹配,有時候僅僅需要匹配某個字元,這樣我們可以用表示式$n ~ /ere/
:
1 2 |
[kodango@devops ~]$ awk '$1 ~ /ko/ {print}' /etc/passwd kodango:x:1000:1000::/home/kodango:/bin/bash |
有時候我們只想顯示特定和行,例如顯示第一行:
1 2 |
[kodango@devops ~]$ seq 1 5 | awk 'NR==1 {print}' 1 |
正規表示式(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在內部維護了許多內建變數,或者稱為系統變數,例如之前提到的FS
、RS
等等。常見的內建變數如下表所示
變數名 | 描述 |
---|---|
ARGC | 命令列引數的各個,即ARGV陣列的長度 |
ARGV | 存放命令列引數 |
CONVFMT | 定義awk內部數值轉換成字串的格式,預設值為”%.6g” |
OFMT | 定義輸出時數值轉換成字串的格式,預設值為”%.6g” |
ENVIRON | 存放系統環境變數的關聯陣列 |
FILENAME | 當前被處理的檔名 |
NR | 記錄的總個數 |
FNR | 當前檔案中的記錄的總個數 |
FS | 欄位分隔符,預設為空白 |
NF | 每個記錄中欄位的個數 |
RS | 記錄的分隔符,預設為回車 |
OFS | 輸出時欄位的分隔符,預設為空白 |
ORS | 輸出時記錄的分隔符,預設為回車 |
RLENGTH | 被match函式匹配的子串長度 |
RSTART | 被match函式匹配的子串位於目標字串的起始下標 |
下面主要介紹幾個比較難理解的內建變數:
1. ARGV
與ARGC
ARGV
與ARGC
的意思比較好理解,就像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. CONVFMT
與OFMT
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. RLENGTH
與RSTART
RLENGTH
與RSTART
都是與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 |
break
和continue
語句,大家應該比較瞭解,分別用於跳出迴圈和跳到下一個迴圈。
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()
:返回0到1的一個隨機數值,不包含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。該函式會定義RSTART
和RLENGTH
兩個內建變數。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
, NR
和FNR
:
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有一個大致的瞭解,歡迎大家一起交流。