awk 指令教學
Last updated on Dec 31, 2022 in Unix-like 命令列教學 by Amo Chen ‐ 3 min read
很多 UNIX-like(Unix, Linux) 程式都是以欄位表格作為執行結果報告,有些時候資料量更可能動輒上百、上千、上萬筆,因此對於 UNIX-like 的使用者而言,如何有效的從這些欄位表格的報告中擷取所需的資料就顯得相當重要,畢竟一行一行細看太花時間成本,也相對沒有效率。
為了幫助我們快速完成資料擷取的工作,在很多 Unix-like 系統中,都有相關指令可以幫忙,例如 cut, grep, sed, awk 等等,其中最值得再進一步深入學習的,大概就是 awk 了。
本篇教學會詳細說明常見的 awk 用法及可能的應用,以使各位對使用 awk 都能有基礎的認識。
取得欄位值
awk 指令在讀取檔案或資料流時,以一行為單位,一次單以一行進行處理,預設的欄位以空白或Tab(\t)進行分隔,分隔完的欄位值從左邊第一欄為 $1
開始,往右遞增累加。在使用 awk 時,除了直接讀取檔案之外,也可以利用管線(pipe, |)將資料流導向給 awk 進行處理。
p.s. $0
代表未經分隔的完整一行, NF
(保留字,無 $ )代表全部欄位數
例如:將 ls -al 結果簡化成檔案名稱對應檔案擁有者
$ ls -al | awk '{print $9"\t=>\t"$3}'
. => user
.. => user
.bash_history => user
.bashrc => user
file.txt => user
BEGIN, END 的用法
上述取得欄位值的例子,也可以利用 BEGIN
關鍵字幫 awk 產出的結果在第一行加上一些說明,更可以使用 END
幫產生的結果加上一些註釋。 BEGIN
在 awk 中所指就是「讀取資料之前」, END
則是代表「讀取完資料之後」。
例如:
$ ls -al | awk 'BEGIN {print "FILE\t=>\tOWNER"} {print $9"\t=>\t"$3} END {print "DONE!\n"}'
FILE => OWNER
. => user
.. => user
awkex1.txt => user
.bash_history => user
.bashrc => user
DONE!
以上的例子就代表「讀取資料之前列印出 FILE => OWNER 」,「資料讀取完畢之後列印出 DONE! 」
if-else
awk 也可以加入 if-else 的條件判斷。例如以下檔案內容:
// Salary.txt
A 19000
B 22000
C 10500
D 65000
$ awk '{if($2 > 22000) {print $1" is rich!"} else {print $1" is poor..."}}' Salary.txt
A is poor...
B is poor...
C is poor...
D is rich!
變數
既然都已經有 if-else 了,當然變數的運用也是跑不掉的!在 awk 中 $(數字)
代表 awk 所讀取當前資料行內的欄位變數,因此其他跟欄位無關的變數,是不需要加錢號(dollar sign, $ )。
繼 if-else 的例子之後,同樣的檔案內容,我們希望統計出薪水低於某些數字以下的人的薪水總和,此時就可以運用變數來進行加總。
$ awk 'BEGIN{SUM=0;} {if($2 <= 22000) SUM+=$2} END {print "Total is ... "SUM}' Salary.txt
Total is ... 51500
上述範例,在 BEGIN
區塊內為變數 SUM 設定初始值,讀取每一行時,進行判斷是否薪水大於 22000,如果大於 22000 則進行累加,讀取完所有資料之後, END
區塊會將變數 SUM 列印出來。
正規表示式
功能強大的 awk 也可以加上正規表示式輔助過濾資料。
基本式 awk '/pattern1/ {action1} /pattern2/ {action2} ... '
以下的範例列出 B 開頭的資料列:
$ awk '/^B.*\s[0-9]*/ {print $0}' Salary.txt
更改欄位分隔依據(field separator)
分隔 CSV 檔案時,所需用到的分隔依據就不再是空白或Tab(\t)了,此時可以使用參數 -F 更改欄位分隔符號。
例如:
$ awk -F ',' '{print $1}' csv_file.txt
取出特定範圍的所有行
對於日誌型的資料,通常會有時間戳記,而且資料會按照順序排列,如果要取出某區間的資料也可以使用 awk 。
例如以下檔案:
# test.txt
a1
1
2
3
4
b1
5
6
7
8
c1
9
10
11
a1
12
13
b1
我們想取出之間的所有行,可以使用以下指令:
$ awk '/a1/, /b1/' test.txt
a1
1
2
3
4
b1
a1
12
13
b1
以上是常被使用的幾種 awk 用法。如果需要更詳細的使用方法,在 man page 中就有很多相關的說明。
參考資料
http://www.grymoire.com/Unix/Awk.html