作為一個程式設計師,寫shell腳本是常有的事。然而什麼樣的shell腳本?什麼態度去寫shell腳本?是我們需要思考的事。如果抱著能跑就行的態度,也許很容易,但是從腳本的健壯性和可靠性上出發,那麼就不是一件容易的事,以下是幾點小技巧。
1、語法檢查
這是最基本的一點,寫shell腳本時,最有效的就是使用相應的工具進行語法自動補全,自動檢查。這樣才能最大程度發現shell腳本中存在的語法錯誤,
2、腳本失敗時即退出
在每一個腳本頭部進行如下設置:
set -e
舉個例子:
#!/bin/bashset -elp #這裡運行會出錯date
遇到這種情況,腳本在運行時一旦出現錯誤就會退出;
$ ./test.shlp: Error - no default destination available.
當然,某些情況下,我們在執行命令遇到失敗時,仍希望它繼續執行下去,這時候就可以臨時加上|| true;
#!/bin/bashset -elp || true date
不過這樣的設置用處不大,很多時候邏輯是需要處理不同的錯誤情況,而這樣要麼錯誤退出,要么正確進行下去,導致無法進入異常狀態的分支。
這種情況下,就可以通過set +e設置回來:
set -e#commandset +e#other command
3、列印腳本執行過程
代碼一定會遇到調試,而要想知道腳本運行時執行了那些命令,每條命令具體執行了什麼,首先可以利用以下命令來執行腳本:
sh -x test.sh
或者直接在腳本開頭添加set -x:
#!/bin/bashset -xif [ $# -lt 1 ]thenecho "no para"else echo "para 1 $1"fi
執行時,輸出如下:
+ [ 0 -le 1 ]+ echo no parano para
輸入結果中帶有+的內容就是實際執行的命令,而且可以清晰的看到比較條件,變量被展開,具體走到了那個分支。
4、顯示未定義的變量
在shell中變量沒有定義,而我們仍然可以使用,但是使用的結果可能不盡人意。
#!/bin/bashif [ "$var" = "abc" ]thenecho " not abc"else echo " abc "fi
這段腳本本來想判斷var的內容是否等於「abc」,而實際上並沒有定義var,而腳本運行到這裡並不會報錯。這時候我們想早點發現這類問題,以減少在複雜的腳本中消耗的精力,可以在開頭加上:
set -u
再次運行就會提示:
test.sh: 5: test.sh: num: parameter not set
再想像一下,你本來想刪除:
rm -rf $dir/*
然後dir是空的時候,變成了什麼?
是不是有種後背發涼的感覺?
5、管道命令一個失敗時整個失敗
有時候我們可能會執行類似這樣的命令:
cat test.sh |grep if | cut -d ';' -f 2
三條命令一行執行,如果我們希望在其中一條失敗,整個命令就失敗,而避免執行後面無意義的命令,那麼可以在開始設置:
set -o pipefail
不設置的情況下,cat test.sh即使執行失敗了,後面的grep實際上還會繼續執行,可能會導致一些意想不到的情況發生,如果不想這樣的情況發生,那麼這樣設置是有幫助的。
6、對於靜態變量使用readonly
通常我們會在腳本開頭定義一些靜態變量:
MY_PATH=/usr/bin
而為了避免MY_PATH被意外修改,可以這樣:
readonly MY_PATH=/usr/bin
這樣的話,一旦後面有命令嘗試修改,就會報錯。
#!/bin/bashreadonly MY_PATH=/usr/binMY_PATH=/usr/local/bin
運行一下試試:
$ ./test.shtest.sh: 3: test.sh: MY_PATH: is read only
看,給你提示了!
7、給變量設置可選的初始值
例如:
name=${1:-shouwang}echo "${name}"
這裡讓name為$1,即第一個參數,而當它為空時,令name為shouwang。
8、多條命令執行使用&&
例如:
cmd0;cmd1;cmd1
這裡如果cmd0失敗了,後面的命令仍然會執行,而如果不希望後面的命令執行,可以使用:
cmd0 && cmd1 && cmd1
9、使用函數
腳本本身比較短還好,而腳本一旦變長,不使用函數,將使得腳本很難維護,可讀性也很差。
10、總結
實際上最開始介紹的腳本檢查工具就已經非常有效了,基本的錯誤都能檢查出來,而其他的內容,更多的是關注於腳本調試,不放過任何一個可能的錯誤。
最後,還是優先推薦shellcheck工具。
歡迎關注軟體特攻隊!