Skip to content

Classic Shell Scripting 讀書筆記(二)

  • Ops

排序文本 sort

locale 對排序的影響

重音位置不同的法文單字

查看字元在 ISO 的八進位數值

分別用傳統 ASCII 和 Canadian-French(系統必須先安裝法文)排序,結果不同

注意空白

-k 指定排序字段時

  • 如未以 -t 指定分隔符號,預設以空白分隔並忽略開頭與結尾的空白
  • 如果指定 -t,則開頭與結尾的空白不會被忽略,例如 ” -X- ” 以 -t " " 分隔,會被分成三個字段: 空白, “-X-” 及空白
  • 如果僅指定一個字段編號,意思是「從該字段開始,一直比對到行的結尾」
  • -k{n},{m} 格式代表「從第 n 個字段開始,至第 m 個字段結尾」
  • -k{n.i},{m.j} 格式代表「從第 n 個字段第 i 個字元開始,至第 m 個字段第 j 個字元結尾」

穩定性

紀錄內的排序字段都相同,但輸出與輸出不一致,代表 sort 並不穩定。GNU 實現了 coreutils 套件,可透過 --stable 彌補這個不足

排除重複

使用 -u 排除重複是基於 key 而非整筆紀錄,如果是後者,則可以搭配 uniq 工具使用

格式化文本 fmt

  • fmt 並不包含在 POSIX 範疇,可透過安裝 coreutils 使用
  • fmt 不同版本,可能會有不同的行為

範例:將短的行合併成長的行

管道

UNIX 工具使用原則就是:想清楚這個問題該如何劃分成更簡單的工作,每個部份是不是已有現成的工具解決。

在 UNIX 下管理相關的文件,大部分都是簡易的文本文件(無須特殊工具即可編輯閱讀)。這些文件通常放在標準目錄 /etc 底下。例如密碼文件(passwd)、群組文件(group)、文件系統加載表(fstab/vfstab)、主機文件(hosts)、默認的 shell 啟動文件(profile)、系統啟動與關機的 shell 腳本

範例:解析 /etc/passwd 的使用者訊息,製作成辦公室名冊

Input

Output

Notes

  1. 在開放程序應養成一個好習慣:僅允許需要用到這個文件的用戶或進程訪問它
  2. 為了避免程序多個實例同時執行導致名稱衝突,使用內置的變量 $$ 程序編號當後綴
  3. 使用 trap 監聽工作中止訊號,清理暫存檔
  4. 這裡使用 = 來做分隔符
  5. - 在管道中代表「上一個」管道的輸出
  6. %-39s% 顯示向左對齊的 39 個字元長度

輸出 HTML 格式

輸出 HTML 格式,所有內容都是可以打印的 ASCII 文字,除了標誌符(identitfier)<>,必須以特殊編碼呈現,稱為實體(entities);實體以 & 開頭,& 也必須轉換成實體

範例:過濾單詞出現的頻率

變量、判斷、重複動作

  • 變量可以用於管理程序狀態,流程控制的功能造就程序語言:如果只有命令語句,是不可能完成任何工作的
  • POSIX Shell 為內嵌(inline)算術提供了一種標記法,稱為算術展開(arithmetic expansion)。會對 $((...)) 的算術表達式進行計算,再將結果放回命令的文本內容

設定、打印環境變數

  • POSIX 標準支援結合 export 與變量宣告,如export FOO=bar,許多商用 UNIX 系統裡的 /bin/sh 仍不兼容 POSIX,最保險的 export 方式是先定義變量再 export

  • 使用 env 打印環境變數不會正確地為變數值加上引號,需要此功能的話請使用 export -p

  • env -i 會重新初始化程序的環境變數,僅傳遞命令行指定的變數

  • myvar= 賦值並不會將 myvar 刪除,unset myvar 才會完全刪除變量

參數展開

  • 參數展開是 Shell 在提供變量值在程序中使用的過程,例如 echo ===${msg}===
  • 在默認情況下,未定義的變量會展開成 null (空字串),開發時須特別留意,如 rm -fr /$MYDIR 這種寫法會引發災難

替換運算符

  • ${count-0}: count 不存在則回傳 0,反之回傳 count 變量的值
  • ${count=0}: count 不存在則將變數 count 的值設為 0
  • ${count?"undefined!"}: count 不存在則顯示 count: undefined! 並退出;省略 message 會出現默認訊息 parameter null or not set
  • ${count+1}: count 存在則回傳 1 (true) 反之回傳 null

以上運算符都可以選用冒號前綴 :,代表「存在且非 null 」

模式匹配運算符

  • ${variable#pattern}: 若模式匹配於變量值的開頭,則刪除匹配的最短部份,回傳剩下的部份
  • ${variable##pattern}: 若模式匹配於變量值的開頭,則刪除匹配的最長部份,回傳剩下的部份
  • ${variable%pattern}: 若模式匹配於變量值的結尾,則刪除匹配的最短部份,回傳剩下的部份
  • ${variable%%pattern}: 若模式匹配於變量值的結尾,則刪除匹配的最長部份,回傳剩下的部份

位置參數

  • 當整數大於 9 時,應該以 {} 括起來,如 ${10}

特殊變量

  • $#: 傳遞到 Shell 腳本或函式的參數總數量
  • $*$@:一次表示所有命令行參數,可以把命令行參數傳遞給腳本或函式
  • "$*":將所有命令行參數視為單個字串,等同於 “$1 $2 …”
  • "$@":將所有命令行參數視為獨立字串,等同於 “$1” “$2″;這是將參數傳送給其他程序的最佳方式,因為保留了所有內嵌在每個參數裡的任何空白
  • $?:前一命令的退出狀態
  • $$:程序的 process ID
  • HOME
  • PATH
  • PWD
  • LANG
  • LC_ALL

shift 命令是用來截去 (lops off) 來自列表的位置參數,由左開始,一旦執行,$1 的初始值會永遠消失

算術展開

  • Shell 的算術運算元與 C 的差不多
  • $(())語法包裝,特殊符號不需要以反斜槓轉義
  • 可利用 () 包裝子表達式
  • 關係運算元如 {<, <=, ==, !=} 回傳值是 1 或 0
  • 注意 i++++i 的差別
  • 注意 {|, &}{||, &&} 的區別
  • &&|| 是快捷 (short-circuit) 運算元,當已判斷出整個語句的真偽時,會立即停止繼續執行命令

退出狀態

if else 語句

  • Algol 68 最有名的地方在於,使用方括號作為開始與結束的關鍵字來組織語句
  • 不要過度簡化而使用 &&|| 取代 if 語句

範例

  • 將驚嘆號置於管道前,表達否定
  • 有些舊的腳本使用冒號(:)命令,代表不做任何事

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *