當我們需要監控服務運行狀態時,一般的策略是寫定時腳本,定時執行探測服務狀態,如果出現預期外情況,就報警。那麼第一步我們就需要學會寫一個監控腳本,這裏我們會講到bash
的執行環境和異常捕獲,以及一些簡單的全局參數。
示例
先看一段shell
代碼,這個監控腳本會時刻監控我們的mysql
進程是否正常服務,每2
分鐘執行一次:
#!/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
等。
我們在使用bash執行腳本的時候,會創建一個新的Shell
,這個Shell
就是腳本的執行環境,並默認提供這個環境的各個參數。
異常捕獲
set -e
set -o pipefail
set -u
set +e
我們的Shell
會給腳本提供默認的環境參數,但是我們也可以用set
命令來修改運行參數。在官方手冊裏一共有十幾個參數,我們介紹常用的四個參數。
如果我們直接在終端運行set
,不帶任何參數,會顯示所有的環境變量和Shell
函數。
開啓和關閉參數
我們常見的類似傳參形式的set -e
代表打開e
代表的環境參數,相反的set +e
代表關閉e
代表的環境參數。
捕獲單行異常
當我們遇到一個異常,如操作不存在的變量或者一行指令執行出錯(行指令返回值不爲0
),Bash
會默認輸出錯誤信息,然後忽略這行錯誤,繼續執行。這在大部分場景下並不是開發者想要的行爲,也不利於腳本的安全和Debug
。我們應該在錯誤出現的時候輸出錯誤信息並中斷執行。這樣能夠防止錯誤被累計和放大。
# 可執行文件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
進程是否存在,輸出不同的值,通過不同的腳本返回值來判斷是否出現故障,併發送報警。當然更好的是,可以在掛掉時,嘗試自動拉起進程。
參考資料
- Bash 腳本 set 命令教程:http://www.ruanyifeng.com/blo...
- 官方手冊:https://www.gnu.org/software/...
- linux中shell變量$#,$@,$0,$1,$2的含義解釋:https://www.cnblogs.com/fhefh...
- linux manpage: command --help