Shell指令碼最佳實踐
0. 編碼、縮排、檔案命名和許可權設定等
使用utf-8編碼;
統一使用tab縮排或空格縮排,不要混用;
檔名以.sh
結尾,並且統一風格;
新增可執行許可權:
chmod +x [bash_script.sh]
最後,在所有輸出完畢後,新增一個空行。
1. 指定預設直譯器
也就是不要省略指令碼第一行的shebang,一般預設是bash:
#!/bin/bash
或者更為通用一些:
#!/usr/bin/env bash
本機可用的shell直譯器,可以通過以下命令檢視:
cat /etc/shells
2. Shell環境設定
設定命令回顯:
set -x
shell預設設定不夠友好,我們希望予以加強。
# 遇到未宣告的變數則報錯停止
set -u
# 遇到執行錯誤則停止
set -e
由於set -e
對管道命令無效,管道命令其中一步失敗則中止,需要使用:
set -o pipefail
我們將這三條合併,構成 bash strict mode,新增在bash指令碼的開始位置:
set -euo pipefail
因為這裡都是shell環境設定,所以也可以在執行指令碼的時候來使用:
bash -euo pipefail [bash_sctipt.sh]
3. 條件判斷。
使用 [[ ]]
並在每個變數和運算子以及和括號之間加入一個空格,例如:
if [[ $# > 1 ]] || [[ $# == 1 && $1 != 'PC' && $1 != 'server' ]]; then
echo 'Invalid commandline arguments, you should use `./run.sh` or `./run.sh PC` or `./run.sh server`'
exit 1
fi
其中,$#
用於獲取命令列引數個數,$N
用於獲取第N個命令列引數,引數$0
指的是指令碼檔名。
相比單方括號,雙方括號的優勢在於可以直接使用比較運算子>``<``==``!=
等,而不是必須使用-gt``-lt``-eq``-ne
;此外雙方括號可以使用&&``||
來表達與和或,而不用必須寫-a``-o
這種難以記憶的寫法。
4. 使用檔案之前判斷是否存在,並進行異常處理。
# 判斷普通檔案存在
if [[ ! -f 'a.txt' ]]; then
touch 'a.txt'
fi
# 判斷資料夾存在
if [[ ! -d 'src' ]]; then
echo 'src dir not found'
exit 1
fi
注意cp -r
命令,在資料夾不存在時回建立資料夾並複製,而當資料夾存在時,會複製到子資料夾內。
5. 迴圈語句。
提倡使用for-in迴圈
# C風格
for (( i=0; i<10; i++)); do
// echo $i
done
# for-in
for i in $(seq 0 9); do
// echo $i
done
和 if 語句的 then 一樣,for 語句的 do 也緊跟在語句後面,不單獨佔一行,這樣顯得比較緊湊。同樣不要忘記加分號。
6. 總是使用main函式包裹執行體
main() {
func1()
func2()
}
main "$@"
與python類似,shell不需要函式入口,可以從第一條指令開始執行。但是為了可讀性和方便除錯,我們總是寫一個命名為main的函式來作為全域性入口。
7. 變數
1)環境變數的設定和取消:
# 設定環境變數
export SKIP_BFS=1
# 取消環境變數
unset SKIP_BFS
2)區域性變數
shell變數預設全域性作用域,這一點與JavaScript類似,函式內宣告區域性變數,應該新增local
關鍵字。
3)使用變數時,總是用雙引號把變數包起來,例如:
# 帶空格的路徑
cp -r "$src_dir" "$dest_dir"
路徑有空格會導致很嚴重的bug,用"$var"
這種寫法,避免了這個問題。
8. 使用$()
而不是反引號獲取表示式的值
如for-in:
# 建議使用 $(seq lb ub) 而不是 `seq lb ub` 獲取範圍
for i in $(seq 0 10) do
echo $i
done
9. 使用 /dev/null
過濾輸出資訊
[expr] > /dev/null 2>&1
命令解釋:重定向到空裝置,並把標準錯誤輸出stderr也重定向為stdout。
注意,2>&1
應該總是放在命令的末尾。
10. case語句等
TBD
更多細節,參考Google Bash風格指南