【伯樂線上轉註】:awk 是一個強大的文字分析工具。它不僅是 Linux 中,也是任何環境中現有的功能最強大的資料處理引擎之一。相對於 grep 的查詢,sed的編輯,awk在其對資料分析並生成報告時,顯得尤為強大。
當你第一次拿起雙手在電腦上使用 awk 命令處理一個或者多個檔案的時候,它會依次讀取檔案的每一行內容, 然後對其進行處理,awk 命令預設從 stdio 標準輸入獲取檔案內容, awk 使用一對單引號來表示 一些可執行的指令碼程式碼,在可執行指令碼程式碼裡面,使用一對花括號來表示一段可執行程式碼塊,可以同時存在多個程式碼塊。 awk 的每個花括號內同時又可以有多個指令,每一個指令用分號分隔,awk 其實就是一個指令碼程式語言。說了這麼多,你肯定還是一臉的懵逼。你猜對了,上面這些都是廢話。先別急,客官請往下看……
awk 命令的基本格式
1 |
awk [options] 'program' file |
options
這個表示一些可選的引數選項,反正就是你愛用不用,不用可以拉到。。。 program
這個表示 awk 的可執行指令碼程式碼,這個是必須要有的。 file
這個表示 awk 需要處理的檔案,注意是純文字檔案,不是你的 mp3,也不是 mp4 啥的。。
先來一個 awk 的使用例子熱熱身
1 |
$ awk '{print $0}' /etc/passwd |
awk 命令的可執行指令碼程式碼使用單引號括起來,緊接著裡面是一對花括號,記住是 “花括號” 不是 “花姑娘”,然後花括號裡面就是一些可執行的指令碼程式碼段,當 awk 每讀取一行之後,它會依次執行雙引號裡面的每個指令碼程式碼段,在上面這個例子中, $0
表示當前行。當你執行了上面的命令之後,它會依次將 /etc/passwd 檔案的每一行內容列印輸出,你一定在想:這有個毛用,用 cat 命令也能搞定。沒錯!上面這個命令沒個毛用,請往下看。
awk 自定義分隔符
awk 預設的分割符為空格和製表符,我們可以使用 -F 引數來指定分隔符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ awk -F ':' '{print $1}' /etc/passwd root bin daemon adm lp sync shutdown halt mail operator games ftp nobody |
上面的命令將 /etc/passwd 檔案中的每一行用冒號 : 分割成多個欄位,然後用 print 將第 1 列欄位的內容列印輸出
如何在 awk 中同時指定多個分隔符
比如現在有這樣一個檔案 some.log 檔案內容如下
1 2 3 4 5 |
Grape(100g)1980 raisins(500g)1990 plum(240g)1997 apricot(180g)2005 nectarine(200g)2008 |
現在我們想將上面的 some.log 檔案中按照 “水果名稱(重量)年份” 來進行分割
1 2 3 4 5 6 |
$ awk -F '[()]' '{print $1, $2, $3}' some.log Grape 100g 1980 raisins 500g 1990 plum 240g 1997 apricot 180g 2005 nectarine 200g 2008 |
在 -F
引數中使用一對方括號來指定多個分隔符,awk 處理 some.log 檔案時就會使用 “(” 和 “)” 來對檔案的每一行進行分割。
awk 內建變數的使用
$0
這個表示文字處理時的當前行
$1
表示文字行被分隔後的第 1 個欄位列
$2
表示文字行被分割後的第 2 個欄位列
$3
表示文字行被分割後的第 3 個欄位列
$n
表示文字行被分割後的第 n 個欄位列
NR
表示檔案中的行號,表示當前是第幾行
NF
表示檔案中的當前行列的個數,類似於 mysql 資料表裡面每一條記錄有多少個欄位
FS
表示 awk 的輸入分隔符,預設分隔符為空格和製表符,你可以對其進行自定義設定
OFS
表示 awk 的輸出分隔符,預設為空格,你也可以對其進行自定義設定
FILENAME
表示當前檔案的檔名稱,如果同時處理多個檔案,它也表示當前檔名稱
比如我們有這麼一個文字檔案 fruit.txt 內容如下,我將用它來向你演示如何使用 awk 命令工具,順便活躍一下此時尷尬的氣氛。。
1 2 3 4 |
peach 100 Mar 1997 China Lemon 150 Jan 1986 America Pear 240 Mar 1990 Janpan avocado 120 Feb 2008 china |
我們來瞧一瞧下面這些簡單到爆炸的例子,這個表示列印輸出檔案的每一整行的內容
1 2 3 4 5 |
$ awk '{print $0}' fruit.txt peach 100 Mar 1997 China Lemon 150 Jan 1986 America Pear 240 Mar 1990 Janpan avocado 120 Feb 2008 china |
下面這個表示列印輸出檔案的每一行的第 1 列內容
1 2 3 4 5 |
$ awk '{print $1}' fruit.txt peach Lemon Pear avocado |
下面面這個表示列印輸出檔案的每一行的第 1 列、第 2 列和第 3 列內容
1 2 3 4 5 |
$ awk '{print $1, $2, $3}' fruit.txt peach 100 Mar Lemon 150 Jan Pear 240 Mar avocado 120 Feb |
其中加入的逗號表示插入輸出分隔符,也就是預設的空格
檔案的每一行的每一列的內容除了可以用 print 命令列印輸出以外,還可以對其進行賦值
1 2 3 4 5 |
$ awk '{$2 = "***"; print $0}' fruit.txt peach *** Mar 1997 China Lemon *** Jan 1986 America Pear *** Mar 1990 Janpan avocado *** Feb 2008 china |
上面的例子就是表示通過對 $2
變數進行重新賦值,來隱藏每一行的第 2 列內容,並且用星號 *
來代替其輸出
在引數列表中加入一些字串或者轉義字元之類的東東
1 2 3 4 5 |
$ awk '{print $1 "\t" $2 "\t" $3}' fruit.txt peach 100 Mar Lemon 150 Jan Pear 240 Mar avocado 120 Feb |
像上面這樣,你可以在 print
的引數列表中加入一些字串或者轉義字元之類的東東,讓輸出的內容格式更漂亮,但一定要記住要使用雙引號。
awk 內建 NR 變數表示每一行的行號
1 2 3 4 5 |
$ awk '{print NR "\t" $0}' fruit.txt 1 peach 100 Mar 1997 China 2 Lemon 150 Jan 1986 America 3 Pear 240 Mar 1990 Janpan 4 avocado 120 Feb 2008 china |
awk 內建 NF 變數表示每一行的列數
1 2 3 4 5 |
$ awk '{print NF "\t" $0}' fruit.txt 5 peach 100 Mar 1997 China 5 Lemon 150 Jan 1986 America 5 Pear 240 Mar 1990 Janpan 5 avocado 120 Feb 2008 china |
awk 中 $NF 變數的使用
1 2 3 4 5 |
$ awk '{print $NF}' fruit.txt China America Janpan china |
上面這個 $NF
就表示每一行的最後一列,因為 NF 表示一行的總列數,在這個檔案裡表示有 5 列,然後在其前面加上 $
符號,就變成了 $5
,表示第 5 列
1 2 3 4 5 |
$ awk '{print $(NF - 1)}' fruit.txt 1997 1986 1990 2008 |
上面 $(NF-1)
表示倒數第 2 列, $(NF-2)
表示倒數第 3 列,依次類推。
現在除了剛才說的有一個 fruit.txt 檔案之外,我們又多了一個新檔案叫 company.txt 內容如下
1 2 3 4 |
yahoo 100 4500 google 150 7500 apple 180 8000 twitter 120 5000 |
我們用 fruit.txt 和 company.txt 兩個檔案來向你演示 awk 同時處理多個檔案的時候有什麼效果
1 2 3 4 5 6 7 8 9 |
$ awk '{print FILENAME "\t" $0}' fruit.txt company.txt fruit.txt peach 100 Mar 1997 China fruit.txt Lemon 150 Jan 1986 America fruit.txt Pear 240 Mar 1990 Janpan fruit.txt avocado 120 Feb 2008 china company.txt yahoo 100 4500 company.txt google 150 7500 company.txt apple 180 8000 company.txt twitter 120 5000 |
當你使用 awk 同時處理多個檔案的時候,它會將多個檔案合併處理,變數 FILENAME
就表示當前文字行所在的檔名稱。
看到這裡是不是感覺 awk 命令的使用方法真的是簡單到爆炸,現在不要太高興,請舉起你的雙手跟我一起搖擺。。。哦,不對!請拿起你的雙手在電腦上試一試上面這些例子。 你會知道我沒有騙你,因為講了這麼多,傻子都會了。。。—_—
BEGIN 關鍵字的使用
在指令碼程式碼段前面使用 BEGIN 關鍵字時,它會在開始讀取一個檔案之前,執行一次 BEGIN 關鍵字後面的指令碼程式碼段, BEGIN 後面的指令碼程式碼段只會執行一次,執行完之後 awk 程式就會退出
1 2 |
$ awk 'BEGIN {print "Start read file"}' /etc/passwd Start read file |
awk 指令碼中可以用多個花括號來執行多個指令碼程式碼,就像下面這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ awk 'BEGIN {print "Start read file"} {print $0}' /etc/passwd Start read file root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin |
END 關鍵字使用方法
awk 的 END 指令和 BEGIN 恰好相反,在 awk 讀取並且處理完檔案的所有內容行之後,才會執行 END 後面的指令碼程式碼段
1 2 |
$ awk 'END {print "End file"}' /etc/passwd End file |
一定要多動手在電腦上敲一敲這些命令,對身體好。腦子是個好東西,要多用。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ awk 'BEGIN {print "Start read file"} {print $0} END {print "End file"}' /etc/passwd Start read file root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin End file |
在 awk 中使用變數
可以在 awk 指令碼中宣告和使用變數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ awk '{msg="hello world"; print msg}' /etc/passwd hello world hello world hello world hello world hello world awk 宣告的變數可以在任何多個花括號指令碼中使用 $ awk 'BEGIN {msg="hello world"} {print msg}' /etc/passwd hello world hello world hello world hello world hello world |
在 awk 中使用數學運算
在 awk 中,像其他程式語言一樣,它也支援一些基本的數學運算操作
1 2 3 4 5 6 |
<span class="hljs-variable"><span class="hljs-variable"><span class="hljs-variable"><span class="hljs-variable">$ </span></span></span></span>awk <span class="hljs-string"><span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'{a = 12; b = 24; print a + b}'</span></span></span></span> company.txt <span class="hljs-number"><span class="hljs-number"><span class="hljs-number"><span class="hljs-number">36</span></span></span></span> <span class="hljs-number"><span class="hljs-number"><span class="hljs-number"><span class="hljs-number">36</span></span></span></span> <span class="hljs-number"><span class="hljs-number"><span class="hljs-number"><span class="hljs-number">36</span></span></span></span> <span class="hljs-number"><span class="hljs-number"><span class="hljs-number"><span class="hljs-number">36</span></span></span></span> |
上面這段指令碼表示,先宣告兩個變數 a = 12 和 b = 24,然後用 print 列印出 a 加上 b 的結果。
看到上面的輸出結果,你很可能又是一臉的懵逼,為什麼會重複輸出 4 次同樣的計算結果。所以說小時不學好,長大做IT。 知識這東西真到了要用的時候,能亮瞎別人的雙眼,好了,不廢話。請記住 awk 是針對檔案的每一行來執行一次單引號 裡面的指令碼程式碼,每讀取到一行就會執行一次,檔案裡面有多少行就會執行多少次,但 BEGIN 和 END 關鍵字後面的 指令碼程式碼除外,如果被處理的檔案中什麼都沒有,那 awk 就一次都不會執行。。。
awk 還支援其他的數學運算子
+
加法運算子-
減法運算子*
乘法運算子/
除法運算子%
取餘運算子
在 awk 中使用條件判斷
比如有一個檔案 company.txt 內容如下
1 2 3 4 |
yahoo 100 4500 google 150 7500 apple 180 8000 twitter 120 5000 |
我們要判斷檔案的第 3 列資料,也就是平均工資小於 5500 的公司,然後將其列印輸出
1 2 3 |
$ awk '$3 < 5500 {print $0}' company.txt yahoo 100 4500 twitter 120 5000 |
上面的命令結果就是平均工資小於 5500 的公司名單, $3 < 5500
表示當第 3 列欄位的內容小於 5500 的時候才會執行後面的 {print $0}
程式碼塊
1 2 |
$ awk '$1 == "yahoo" {print $0}' company.txt yahoo 100 4500 |
awk 還有一些其他的條件操作符如下
<
小於<=
小於或等於==
等於!=
不等於>
大於>=
大於或等於~
匹配正規表示式!~
不匹配正規表示式
使用 if 指令判斷來實現上面同樣的效果
1 2 3 |
$ awk '{if ($3 < 5500) print $0}' company.txt yahoo 100 4500 twitter 120 5000 |
上面表示如果第 3 列欄位小於 5500 的時候就會執行後面的 print $0
,很像 C 語言和 PHP 的語法對不對。 想到這裡有一句話不知當講不當講,那就是 PHP 是世界上最好的語言。。。 我可能喝多了, 但是突然想起來我好像從來不喝酒。。。—_—
在 awk 中使用正規表示式
在 awk 中支援正規表示式的使用,如果你還對正規表示式不是很瞭解,請先停下來,上 google 去搜一下。
比如現在我們有這麼一個檔案 poetry.txt 裡面都是我寫的詩,不要問我為什麼那麼的有才華。內容如下:
1 2 3 4 |
This above all: to thine self be true There is nothing either good or bad, but thinking makes it so There’s a special providence in the fall of a sparrow No matter how dark long, may eventually in the day arrival |
使用正規表示式匹配字串 “There” ,將包含這個字串的行列印並輸出
1 2 3 |
$ awk '/There/{print $0}' poetry.txt There is nothing either good or bad, but thinking makes it so There’s a special providence in the fall of a sparrow |
使用正規表示式配一個包含字母 t 和字母 e ,並且 t 和 e 中間只能有任意單個字元的行
1 2 3 4 |
$ awk '/t.e/{print $0}' poetry.txt There is nothing either good or bad, but thinking makes it so There’s a special providence in the fall of a sparrow No matter how dark long, may eventually in the day arrival |
如果只想匹配單純的字串 “t.e”, 那正規表示式就是這樣的 /t\.e/
,用反斜槓來轉義 .
符號 因為 .
在正規表示式裡面表示任意單個字元。
使用正規表示式來匹配所有以 “The” 字串開頭的行
1 2 3 |
$ awk '/^The/{print $0}' poetry.txt There is nothing either good or bad, but thinking makes it so There’s a special providence in the fall of a sparrow |
在正規表示式中 ^
表示以某某字元或者字串開頭。
使用正規表示式來匹配所有以 “true” 字串結尾的行
1 2 |
$ awk '/true$/{print $0}' poetry.txt This above all: to thine self be true |
在正規表示式中 $
表示以某某字元或者字串結尾。
又一個正規表示式例子如下
1 2 |
$ awk '/m[a]t/{print $0}' poetry.txt No matter how dark long, may eventually in the day arrival |
上面這個正規表示式 /m[a]t/
表示匹配包含字元 m ,然後接著後面還要包含中間方括號中表示的單個字元 a ,最後還要包含字元 t 的行,輸出結果中只有單詞 “matter” 符合這個正規表示式的匹配。因為正規表示式 [a]
方括號中表示匹配裡面的任意單個字元。
繼續上面的一個新例子如下
1 2 3 4 |
$ awk '/^Th[ie]/{print $0}' poetry.txt This above all: to thine self be true There is nothing either good or bad, but thinking makes it so There’s a special providence in the fall of a sparrow |
這個例子中的正規表示式 /^Th[ie]/
表示匹配以字串 “Thi” 或者 “The” 開頭的行,正規表示式方括號中表示匹配其中的任意單個字元。
再繼續上面的新的用法
1 2 3 4 |
$ awk '/s[a-z]/{print $0}' poetry.txt This above all: to thine self be true There is nothing either good or bad, but thinking makes it so There’s a special providence in the fall of a sparrow |
正規表示式 /s[a-z]/
表示匹配包含字元 s 然後後面跟著任意 a 到 z 之間的單個字元的字串,比如 “se”, “so”, “sp” 等等。
正規表示式 []
方括號中還有一些其他用法比如下面這些
1 2 |
[a-zA-Z] 表示匹配小寫的 a 到 z 之間的單個字元,或者大寫的 A 到 Z 之間的單個字元 [^a-z] 符號 `^` 在方括號裡面表示取反,也就是非的意思,表示匹配任何非 a 到 z 之間的單個字元 |
正規表示式中的星號 *
和加號 +
的使用方法
1 2 |
$ awk '/go*d/{print $0}' poetry.txt There is nothing either good or bad, but thinking makes it so |
上面這個表示匹配包含字串 “god”,並且中間的字母 “o” 可以出現 0 次或者多次,比如單詞 “good” 就符合這個要求。 正規表示式中的 +
和星號原理差不多,只是加號表示任意 1 個或者 1 個以上,也就是必須至少要出現一次。
正規表示式問號 ? 的使用方法
1 2 |
$ awk '/ba?d/{print $0}' poetry.txt There is nothing either good or bad, but thinking makes it so |
正規表示式中的問號 ? 表示它前面的字元只能出現 0 次 或者 1 次,也就是可以不出現,也可以出現,但如果有出現也只能出現 1 次。
正規表示式中的 {} 花括號用法
1 2 |
$ awk '/go{2}d/{print $0}' poetry.txt There is nothing either good or bad, but thinking makes it so |
花括號 {}
表示規定它前面的字元必須出現的次數,像上面這個 /go{2}d/
就表示只匹配字串 “good”,也就是中間的字母 “o” 必須要出現 2 次。
正規表示式中的花括號還有一些其他的用法如下
1 2 3 |
/go{2,3}d/ 表示字母 "o" 只能可以出現 2 次或者 3 次 /go{2,10}d/ 表示字母 "o" 只能可以出現 2次,3次,4次,5次,6次 ... 一直到 10 次 /go{2,}d/ 表示字母 "o" 必須至少出現 2 次或著 2 次以上 |
正規表示式中的圓括號的用法
1 2 |
$ awk '/th(in){1}king/{print $0}' poetry.txt There is nothing either good or bad, but thinking makes it so |
正規表示式中的圓括號表示將多個字元當成一個完整的物件來看待。比如 /th(in){1}king/
就表示其中字串 “in” 必須出現 1 次。而如果不加圓括號就變成了 /thin{1}king/
這個就表示其中字元 “n” 必須出現 1 次。
看到這裡,如果你對 poetry.txt 件中寫的詩比較熟悉,你就會發現。。。我去!這詩根本就不是我寫的。所以論多讀書是多麼的重要。我有幸借用莎士比亞的詩來向你講解如何在 awk 中使用正規表示式。現在該想想晚上吃什麼,晚上去吃火鍋。。。—_—
使用 awk 的一些總結
因為 awk 算起來也是一種程式語言,它的功能遠遠不止我們上面講的這些,awk 還有一些其他比較複雜的功能。但一般我們不建議將 awk 用的太過於複雜。通常面對一些比較複雜的場景我們還是要使用其他的一些工具,比如 shell 指令碼,Lua 等等……