實現簡單的監控腳本(Bash的執行和異常捕獲)

當我們需要監控服務運行狀態時,一般的策略是寫定時腳本,定時執行探測服務狀態,如果出現預期外情況,就報警。那麼第一步我們就需要學會寫一個監控腳本,這裏我們會講到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進程是否存在,輸出不同的值,通過不同的腳本返回值來判斷是否出現故障,併發送報警。當然更好的是,可以在掛掉時,嘗試自動拉起進程。

參考資料

  1. Bash 腳本 set 命令教程:http://www.ruanyifeng.com/blo...
  2. 官方手冊:https://www.gnu.org/software/...
  3. linux中shell變量$#,$@,$0,$1,$2的含義解釋:https://www.cnblogs.com/fhefh...
  4. linux manpage: command --help
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章