當咱們須要監控服務運行狀態時,通常的策略是寫定時腳本,定時執行探測服務狀態,若是出現預期外狀況,就報警。那麼第一步咱們就須要學會寫一個監控腳本,這裏咱們會講到bash
的執行環境和異常捕獲,以及一些簡單的全局參數。html
先看一段shell
代碼,這個監控腳本會時刻監控咱們的mysql
進程是否正常服務,每2
分鐘執行一次:node
#!/bin/bash #設置異常的捕獲和退出 set -e set -o pipefail set -u #獲取當前腳本執行的命令和路徑 #self_name=`readlink -f $0` #self_path=`dirname $self_name` set +e # 腳本主體 mysql_process_num=`ps aux | grep mysql | grep -v grep | grep -v bash | wc -l` set -e # 判斷腳本輸出,此處0爲異常 if [ "$mysql_process_num" -ge 1 ]; then echo "$mysql_process_num|proc_name=mysql" else echo "0|proc_name=mysql" fi
#!/bin/bash
首行表示此腳本使用/bin/sh
來解釋執行,#!
是特殊的標識符,後跟此腳本解釋器的路徑。
相似的還有/bin/sh, /bin/perl, /bin/awk
等。mysql
咱們在使用bash執行腳本的時候,會建立一個新的Shell
,這個Shell
就是腳本的執行環境,並默認提供這個環境的各個參數。linux
set -e set -o pipefail set -u set +e
咱們的Shell
會給腳本提供默認的環境參數,可是咱們也能夠用set
命令來修改運行參數。在官方手冊裏一共有十幾個參數,咱們介紹經常使用的四個參數。sql
若是咱們直接在終端運行set
,不帶任何參數,會顯示全部的環境變量和Shell
函數。shell
咱們常見的相似傳參形式的set -e
表明打開e
表明的環境參數,相反的set +e
表明關閉e
表明的環境參數。安全
當咱們遇到一個異常,如操做不存在的變量或者一行指令執行出錯(行指令返回值不爲0
),Bash
會默認輸出錯誤信息,而後忽略這行錯誤,繼續執行。這在大部分場景下並非開發者想要的行爲,也不利於腳本的安全和Debug
。咱們應該在錯誤出現的時候輸出錯誤信息並中斷執行。這樣可以防止錯誤被累計和放大。bash
# 可執行文件run #!/bin/bash # 調用未定義的命令 foo echo bar # 執行該文件 $ ./run ./run: line 3: foo: command not found bar
能夠看到輸出了錯誤信息,並繼續執行。併發
若是咱們想保證單行若是出現錯誤,就中斷執行腳本,能夠有三種寫法:函數
# 方法一 command || exit 1 # 方法二 if ! command; then exit 1; fi # 方法三 command if [ "$?" -ne 0 ]; then exit 1; fi
上面的方法統一爲判斷一行指令返回值是否爲0
來判斷異常。
相似的,若是咱們的多個命令有依賴關係,即後者的執行須要前者成功,則須要寫:
command1 && command2
上面的這種寫法過於複雜,若是咱們有一段腳本,則每行都須要單獨判斷,因此咱們須要使用全局的捕獲方式。
set -e
會根據返回值來判斷命令是否失敗,只要腳本發生錯誤,就會終止繼續執行:
# 可執行文件run #!/bin/bash set -e foo echo bar # 執行該文件 $ ./run ./run: line 3: foo: command not found
能夠看到腳本在發生錯誤後終止了執行。
若是咱們有一些代碼返回值爲0
也不表明失敗,能夠先使用set +e
關閉這個參數,稍後再打開。或者使用:
foo || true
set -e
不適合管道命令,所謂管道命令就是經過管道運算符|
將不一樣功能的指令組合成一個複雜命令。好比:
# 查看全部進程,過濾包含mysql字段的進程,並對過濾後的進程數量計數 ps aux | grep mysql | wc -l
Bash
會將最後一個子命令的返回值做爲整個命令的返回值。也就是若是中間的子命令出錯了,只要最後一個子命令返回值爲0
,那麼異常便不會中斷整個腳本:
# 可執行文件run #!/bin/bash set -e #set -o pipefail foo | echo abc echo bar # 執行該文件 $ ./run abc ./run: line 4: foo: command not found bar
當咱們執行腳本時,遇到未定義的變量,Bash
會默認忽略,並繼續執行。設置set -u
參數,可以捕獲不存在的變量的錯誤:
# 可執行文件run #!/bin/bash set -e set -u echo $a echo bar # 執行該文件 $ ./run ./run: line 4: a: unbound variable
若是咱們的腳本須要輸出不少東西,那麼你在終端只能看到連續輸出的內容,而沒法知道是哪一行指令輸出的結果。set -x
參數可讓咱們先輸出執行的命令,再輸出結果。
# 可執行文件run #!/bin/bash set -x echo `ps aux | grep mysql` echo bar # 執行該文件 $ ./run ++ ps aux ++ grep mysql + echo work 5191 0.0 0.0 106060 1464 '?' S May31 0:00 /bin/sh bin/mysqld_safe --defaults-file=my.cnf work 5191 0.0 0.0 106060 1464 ? S May31 0:00 /bin/sh bin/mysqld_safe --defaults-file=my.cnf + echo bar bar
set -e, set -u, set -o
這些都是指令的簡稱,常規的寫法是set -o option-name
,有時候咱們使用常規的寫法可讀性更高,有時候串起來使用更方便:set -eux
。
咱們能夠經過官方手冊的-o
參數看到全稱:
-e: -o errexit -u: -o nounset -x: -o xtrace
咱們也能夠在執行該腳本時手動指定:
bash -euxo pipefail run
#獲取當前腳本執行的命令和路徑 #self_name=`readlink -f $0` #self_path=`dirname $self_name`
首先須要瞭解到$0
是腳本的執行文件路徑,相似的還有$?
指最後的命令的返回值,$-
是set
命令設置的全部Flag
。
# 可執行文件run #!/bin/bash echo $0 # 執行該文件 $ ../test/run ../test/run
readlink
爲輸出符號連接的權威文件名,-f
爲遞歸找到最終的文件名,如:
ln -s /home/work/run /home/work/run2 # 可執行文件run #!/bin/bash echo `readlink -f $0` # 執行該文件 $ ./run2 /home/work/run
dirname
輸出已經去除了尾部的"/"
字符部分的名稱;若是名稱中不包含"/"
,
則顯示"."
(表示當前目錄)。如:
dirname /usr/bin/sort 輸出"/usr/bin"。 dirname stdio.h 輸出"."。
後面的就是判斷mysql
進程是否存在,輸出不一樣的值,經過不一樣的腳本返回值來判斷是否出現故障,併發送報警。固然更好的是,能夠在掛掉時,嘗試自動拉起進程。