Classic Shell Scripting 讀書筆記 (五)

  • Devops

AWK

  • 所有 UNIX 系統裡都至少有一套 awk
  • awk 是 POSIX 的一部分
  • 把輸入流看成一連串紀錄(record)的集合,每筆紀錄可以更進一步細分成字段(field)
  • 如何構成一筆紀錄和一個字段,是可控制的,甚至可以在處理期間修改
  • 替使用者妥善處理每個文件的開啟、讀取與關閉
  • 強大的功能大多是具備對正則表達式的支援

命令行

  • 慣例是將 -F 當作第一個選項,或者也可以設定變量 FS 來重新定義分隔符

    以上面例子來看,-F 應用到第一個文件組,而 FS 應用到第二個文件組

  • -- 是特殊選項,指出 awk 本身已經沒有更進一步的命令行選項

  • 初始化的 -v 必須放在 program 之前,在文件處理之前生效
  • program 使用單引號,保護內容不被 Shell 解釋,不過要特別注意程序是否本身包含單引號

程序元素

  • 提供標量(scalar)、陣列(array)兩種資料結構
  • 提供語句類型:賦值、註釋、條件、函數、輸入、循環、輸出

字串表達式

  • 以雙引號定界
  • 長度沒有任何限制,視內存而定
  • 賦值後舊字串的內存空間會自動回收
  • 字串的比較視字串長短而定,如 “A” < “AA” 返回 1
  • awk 無字串連接的運算符,多個連續的字串,會自動連接在一起
  • 數字轉字串,可透過連接空字串,如 s = “” + 123

正則匹配語句

  • 提供兩個運算符: ~!~ 分別代表匹配和不匹配
  • 正則常量可以用雙引號或斜槓 / 定界,斜槓形式比較常見,因為可以用來強調括起來的就是正則表達式
  • 字面意義的引號、斜槓、反斜槓在正則裡都應該被保護,兩種形式的保護方式可能不同,例如 "\\\\TeX"/\\Tex/ 都是表示正則的 \Tex

數值表達式

  • 所有 awk 裡的數字都以雙精確度的浮點值(對應於 C 的 double)表示
  • awk 在 IEEE 754 廣泛可用前就以開發,無法完整支持 Infinity 與 NaN
  • 浮點數可以包含一個末端以字母 e(或 E)所表示的十次方指數,例如 0.03125、3.125e-2、0.003125E1
  • 字串轉數字,可透過連接0,如 n = 0 + “123”

IEEE 754

  • 64 位雙精度的值包含 1 位正負號、11 位偏移指數,以及 53 位顯著位,表示最多可存 16 位的十進制數字 (2^53)
  • 值太大無法表示,會顯示 overflow 並回傳 Infinity
  • 值未正確定義,如 Infinity – Infinity 或 0 / 0 則結果是 NaN
  • 相同正負號的 Infinity 比較時是相等的,NaN 比較時則不等於它自己(假設 x 為 NaN,則 x != x)

數值運算符

  • 指數運算與賦值運算具有右結合性,如 a^b^c^d 意即 a^(b^(c^d))
  • ****= 運算符並不在 POSIX 標準內,請用 ^^= 取代
  • 支援三元條件式,如 a = (u>w)?x^3:y^7

標量變量(scalar)

  • 所有 awk 變量在建立時的初始值為空字串,但是當需要數值時,它會被視為 0
  • 命名規則:局部變量為小寫、全域變量第一個字母為大寫、內建量全為大寫

數組變量(array)

  • 以方括號將任意數字或字串作為索引,稱為關聯數組(key-value pair)
  • 儲存空間是稀疏的(sparse),聲明的元素才會被配置
  • delete array[index]delete array 回收空間,但數組名稱不會被刪除
  • 一個變量不能同時用作標量變量和數組變量
  • 不支持多維數組如 array[index1][index2],但支援多個索引如 array[index1, index2]
  • 針對逗號分隔的多個索引,awk 將其視為一個字串,以內建變量 SUBSEP 取代逗點,例如 array[index1, index2] 的索引會轉換成 index1 SEBSEP index2,預設的 SUBSEP 是 \034
  • 為了避免 SUBSEP 被修改,每個程序應該只在 BEGIN 操作裡設置一次

命令行參數

awk 透過內建變量 ARGC(參數計數) 與 ARGV(參數值),讓命令行參數變得可用,以下方範例說明:

須注意的是,program、與 -f 或 -v 選項結合的參數是不可使用的

環境變量

awk 提供內建數組 ENVIRON 來新增、修改及刪除環境變量,但此數組的變更無法傳遞給子進程,因此應該只做讀取使用(readonly)

分隔符

  • awk 以內建變量 RS 分隔紀錄(在 POSIX 中為單一字串,預設為換行),再以內建變量 FS (正則表達式,默認為空格)分隔字段
  • gawk、mawk 支援 RS 可以是正則表達式
  • FS 只有在超過一個字符時,才會被視為正則表達式,如不會視 FS = "." 為正則
  • 使用預設 FS 分隔字段時,連續的空格(如 tab)、及行的開頭及結尾的空白會被自動忽略,除非明確指定只匹配一個空格 FS = "[ ]"

字段

  • 字段可以透過特殊名稱 $1、$2、$NF 使用,此名稱會轉換成整數,如 $3.14 及 $(27/9) 都會引用到第三個字段
  • 不帶$號的 NF 變量代表一筆紀錄的字段數
  • 名稱 $0 引用到當前紀錄
  • 引用大於 NF 範圍以外的名稱會回傳空字串,引用小於 0 的負值字段編號則會噴錯

模式與操作

  • 可以使用逗號隔開的兩個表達式來選取範圍(range expression)
  • 在 BEGIN 區塊裡,引用 FILENAME、FNR、NF、NR 會回傳 NULL 或 0,因為初始值尚未定義
  • 在 END 區塊裡,FILENAME 是最後一個要處理的輸入文件,FNR、NF、NR 則會保留最後一筆輸入紀錄而來的值

以下這些模式/操作組合的結果是一樣的:

改變輸出的分隔符,至少須指定一個字段(即使沒有做任何變更):

常見單行操作

語句

  • 連續語句的執行,以單行程序的方式通常用分號隔開,以文件提供的方式通常是一句一行
  • 浮點數通常不精確,應避免在 for 語句表達式裡計算非整數的值
  • awk 迴圈不支援 shell 風格的 break ncontinue n

幾個基本的複合語句:

  • expr1: 循環開始之前計算
  • expr2: 每次循環開始時計算,若非零則循環繼續
  • expr3: 每次循環結束時計算

註:三個表達式皆可以為空

腳本範例:整數因數分解

用戶控制的輸入 getline

  • getline var: 從當前輸入讀取下一條紀錄,並存入 var,並更新 NR / FNR
  • getline < file: 從 file 中讀取下一條紀錄,存入 $0,並更新 NF
  • getline var < file: 從 file 中讀取下一條紀錄,存入 var

範例:讀取並檢查輸入

如果要確保輸入來自終端而非標準輸入,可改用 getline answer < “dev/tty”

範例:讀取並存入陣列

當輸入讀取成功時,返回 +1;當讀取為文件結尾,返回 0;返回 -1 表示錯誤

範例:配合管道

在 awk 裡,文件一旦打開後,便會一直保持在打開狀態,而大部分系統會限制打開文件的個數,因此當使用的管道通過時,應透過 close 函數關閉文件。

由於輸入是來自管道,因此關閉輸出管道的操作是在完成時立刻執行。如果需要在相同程序中讀取輸出,可以透過臨時性的文件,例如:

調用外部程序

使用 system 命令

  • 先清除所有緩衝區輸出,然後啟動一個 /bin/sh 實例執行命令
  • 每次調用都會啟動一個新的 Shell
  • 傳遞數據至 Shell 只能透過中間文件(中間文件必須在調用 system 之前關閉,確保任何緩衝區輸出皆已正確寫入)
  • 不能指定 Shell

另一種解法,使用管道傳輸到 Shell,能指定 Shell

自訂函數

  • 慣例是將所有函數放在成對的模式/操作碼之後,並按照字母排序
  • 函數中使用但未列在參數列表中的變量,會被視為全域 (global) 的
  • 「額外的參數」會被視為局部(local)的,初始值是空字串,慣例是將他們同樣列在參數列表中,並在字首前保留一些空白

範例:數組查找

範例:遞迴(resursion)

其他常用函數

  • substr(string, stasrt, len): 提取子字串
  • tolower(string)toupper(string):大小寫轉換
  • index(string, find):尋找第一個出現的子字串
  • match(string, regexp):字串匹配
  • split(string, array, regexp):分割字串(注意 “” 與 “[ ]” 的差異)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。