当咱们须要监控服务运行状态时,通常的策略是写定时脚本,定时执行探测服务状态,若是出现预期外状况,就报警。那么第一步咱们就须要学会写一个监控脚本,这里咱们会讲到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
进程是否存在,输出不一样的值,经过不一样的脚本返回值来判断是否出现故障,并发送报警。固然更好的是,能够在挂掉时,尝试自动拉起进程。