Shell指令碼最佳實踐
阿新 • • 發佈:2020-08-23
# Shell指令碼最佳實踐
## 0. 編碼、縮排、檔案命名和許可權設定等
使用utf-8編碼;
統一使用tab縮排或空格縮排,不要混用;
檔名以`.sh`結尾,並且統一風格;
新增可執行許可權:
```bash
chmod +x [bash_script.sh]
```
最後,在所有輸出完畢後,新增一個空行。
## 1. 指定預設直譯器
也就是不要省略指令碼第一行的shebang,一般預設是bash:
```bash
#!/bin/bash
```
或者更為通用一些:
```bash
#!/usr/bin/env bash
```
本機可用的shell直譯器,可以通過以下命令檢視:
```bash
cat /etc/shells
```
## 2. Shell環境設定
設定命令回顯:
```bash
set -x
```
shell預設設定不夠友好,我們希望予以加強。
```bash
# 遇到未宣告的變數則報錯停止
set -u
# 遇到執行錯誤則停止
set -e
```
由於`set -e`對管道命令無效,管道命令其中一步失敗則中止,需要使用:
```bash
set -o pipefail
```
我們將這三條合併,構成 [bash strict mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/),新增在bash指令碼的開始位置:
```bash
set -euo pipefail
```
因為這裡都是shell環境設定,所以也可以在執行指令碼的時候來使用:
```bash
bash -euo pipefail [bash_sctipt.sh]
```
## 3. 條件判斷。
使用 `[[ ]]` 並在每個變數和運算子以及和括號之間加入一個空格,例如:
```bash
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迴圈
```bash
# 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函式包裹執行體
```bash
main() {
func1()
func2()
}
main "$@"
```
與python類似,shell不需要函式入口,可以從第一條指令開始執行。但是為了可讀性和方便除錯,我們總是寫一個命名為main的函式來作為全域性入口。
## 7. 變數
1)環境變數的設定和取消:
```bash
# 設定環境變數
export SKIP_BFS=1
# 取消環境變數
unset SKIP_BFS
```
2)區域性變數
shell變數預設全域性作用域,這一點與JavaScript類似,函式內宣告區域性變數,應該新增`local`關鍵字。
3)使用變數時,總是用雙引號把變數包起來,例如:
```bash
# 帶空格的路徑
cp -r "$src_dir" "$dest_dir"
```
路徑有空格會導致很嚴重的bug,用`"$var"`這種寫法,避免了這個問題。
## 8. 使用`$()`而不是反引號獲取表示式的值
如for-in:
```bash
# 建議使用 $(seq lb ub) 而不是 `seq lb ub` 獲取範圍
for i in $(seq 0 10) do
echo $i
done
```
## 9. 使用 `/dev/null` 過濾輸出資訊
```bash
[expr] > /dev/null 2>&1
```
命令解釋:重定向到空裝置,並把標準錯誤輸出stderr也重定向為stdout。
注意,`2>&1`應該總是放在命令的末尾。
## 10. case語句等
TBD
更多細節,參考[Google Bash風格指南](https://google.github.io/styleguide/shellguid