awk 是一套用以處理文字檔案的優秀 Unix 腳本語言,在多數 Linux 散佈套件中的版本是簡稱為 gawk 的 GNU awk。awk 將檔案中的每一行視為個別的記錄,一行中的每一個項目則是個別的欄位。如此一來就可以用各種彈性的方式處理你的檔案。經典的方式是以 /etc/passwd 來作說明,以下範例會輸出該檔案所有內容:
$ awk '{ print $0 }' /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh
不帶參數的 print 指令表示輸出一整行,$0 意思也是一整行,所以忽略 $0 同樣會得到一樣結果。假設你現在只想要用戶名稱與 UID 列表:由於 /etc/passwd 的資料欄位是以冒號分隔,因此很適合用 awk 來解析。你只需要從左邊從 1 開始數到你想輸出的欄位,然後像這樣截取出用戶名稱與 UID:
$ awk -F":" '{ print $1 " " $3 }' /etc/passwd root 0 daemon 1 bin 2 sys 3
-F 用來定義欄位分隔字元," " 則插入空白。想要先列出 UID?簡單,交換變數順序即可:
$ awk -F":" '{ print $3 " " $1 }' /etc/passwd
現在讓我們看看運用 awk 把一大堆財務數據匯入 GnuCash 的實際例子。
GnuCash 是自由與開放源碼軟體中的瑰寶之一,是一套執行於 Linux、Mac、Windows 的強大會計軟體。你可以匯入 QIF 與 OFX 檔案,不過說來可惜,儘管 CSV 是財務資料匯出匯入的通用格式,許多財務應用軟體都支援 CSV 匯出入,GnuCash 對 CSV 匯入卻僅提供部份支援。
假設你有一個包含歷年財務資料的龐大試算表,你希望將資料放進適當的會計軟體中,或是你想要把資料從某個會計軟體,匯入到 GnuCash 之中,但該軟體僅支援 CSV 匯出。你可以選擇重新鍵入所有資料,或是讓你的 Linux 腳本語言技術派上用場。流程是先產生 CSV 檔案,然後轉成 QIF 格式,再將 QIF 檔案匯入 GnuCash。GnuCash 對於 QIF 檔案正確性相當挑剔,因此得確保檔案內容無誤。
先確定複製一份你的原始檔案,不要破壞了原始檔案。
我會讓例子盡量簡單,僅使用以下的 QIF 欄位:
D - date P - payee M - memo T - amount N - check number, or any notation in the check number field L - category, which corresponds to GnuCash accounts ^ - end of record
我們還需要在 QIF 檔頭中指定會計形態,如:
!Type:Bank !Type:Cash !Type:CCard !Type:Invst
QIF 規格支援更多項目,你可在 https://www.respmech.com/mym2qifw/qif_new.htm 找到完整列表。或者用 qif spec 作為關鍵字去查詢相關資料。
轉成 QIF 格式前,先確認你的 CSV 檔案格式是否正確。確認你的提款前有減號,例如 -33.72,而且不要使用錢字符號,存款前的加號可有可無,隨你喜歡。你的存提款必須在同一欄位。最後的 CSV 匯出檔案像這樣:
11/03/2008 Copy Junction Copy of building codes -33.72 8732 Supplies 11/03/2008 Home Depot Trowel -17.05 8734 Tools 11/03/2008 Dewalt Service Center Charger for Drill -75.85 8735 Tools 11/04/2008 Building Supply Margin trowel -13.23 8736 Tools 11/05/2008 Jane Smith invoice #5843 8,500.00 dep income:contracting
如果 QIF 檔案中有任何錯誤,GnuCash 就無法成功匯入。我碰過的問題有,用了多個減號如 --33.72,額外的小數點,日期格式錯誤。awk 不會理會這些問題,但 GnuCash 會。準備好之後,將 CSV 檔案轉成 QIF 格式:
$ ( echo '!Type:Bank'; cat exportfile.csv | awk -F $'\t' '{ print "D" $1; print "P" $2; print "M" $3; print "T" $4; print "N" $5; print "L" $6; print "^"; }' ) > importfile.qif
結果像這樣,提款以減號表示,存款則無符號:
!Type:Bank D03/25/2008 PJane Smith M invoice #4657 T4000.00 Ndep Lincome:contracting ^ D04/02/2008 PFirst Bank of Money MCheck Order T-21.44 NACH Lbank fees ^ D05/15/2008 PPretty Designs MDesign Services T-500.00 N8922 LContract Services ^
注意到如何使用針對水平製表 \t 的 ASCII 跳脫序列,來指定以 tab 鍵作為欄位分隔符號。如果一切無誤,你會得到可匯入 GnuCash 的 QIF 檔案。
awk 在搜尋文字區塊上有著優秀能力,當 grep 沒辦法得到你想要的結果時,你可以試試 awk。例如,你想要從完整 lspci 輸出中找出特定設備:
$ lspci -v | awk '/VGA/,/^$/' 01:00.0 VGA compatible controller: NVIDIA Corporation G98 [GeForce 8400 GS] (rev a1) (prog-if 00 [VGA controller]) Subsystem: Micro-Star International Co., Ltd. Device 1162 Flags: bus master, fast devsel, latency 0, IRQ 18 Memory at fd000000 (32-bit, non-prefetchable) [size=16M] Memory at d0000000 (64-bit, prefetchable) [size=256M] Memory at fa000000 (64-bit, non-prefetchable) [size=32M] I/O ports at dc00 [size=128] [virtual] Expansion ROM at fe9e0000 [disabled] [size=128K] Capabilities: Kernel driver in use: nvidia Kernel modules: nvidia_current, nouveau, nvidiafb
插入符號 ^ 正規表示式的定位符,用來對應字串起始處,$ 則對應終止處。所以上例中 /^$/ 會找出文字區塊起始與終止處的斷行。這是從以空白區隔各個段落的大型設定檔案中,截取特定區塊的漂亮技巧,例如 sshd_config:
$ awk '/X11Forwarding/, /^$/' /etc/ssh/sshd_config X11Forwarding yes X11DisplayOffset 10 PrintMotd no PrintLastLog yes TCPKeepAlive yes #UseLogin no
我們經常使用 sort 與 uniq 指令,從檔案中找出並移除重複項目。不過如果你不希望你的原始檔被排序或更動,這時正是 awk 派上用場的時候,我們可以用 awk 截取不重複記錄並儲存在新的檔案中:
$ awk '!x[$0]++' filewithdupes > newfile
你的原始檔案原封不動,新檔案僅包含依序排列的不重複項目。
man awk 可以查到完整的選項說明,要發揮 awk 或任何 Unix/Linux 指令的大部份功能,你需要對正規表示式有不錯的理解。對此我推薦 Mastering Regular Expressions 這本書。如果你想挑最有用的東西來學,就學正規表示式吧,因為多數程式語言和 Linux/Unix 指令都用得到正規表示式。
Comments