Shell信号捕获技术的运用

说明

shell语言有些特点,返回值是0表示当前shell命令执行成功,返回值非零大多数情况下表示当前shell命令执行出现了非预期的情况。同时当shell脚本出现这种非预期情况时,默认情况下shell会继续执行下一行代码。

问题场景

例如:

$ ls
script.sh
$ cat script.sh 
#!/bin/bash
cp dev.conf prod.conf
echo 'this is a line configuration' >> actual.conf
$ ./script.sh 
cp: cannot stat ‘dev.conf’: No such file or directory
$ ls
prod.conf  script.sh
$ cat prod.conf 
this is a line configuration

以上代码中,如果由于某种原因(如rpm包的安装)dev.conf 文件没有创建,那么cp命令就是返回非零码。shell仍然继续执行下面一行重定向命令。
生产环境中,就曾遇到如下一些问题:
1)机房由于网络环境配置问题,导致配置模板中的添加默认路由的shell函数 add_ali_public_net_route 执行失败,即返回非零。这个模板在实验室已经测试通过,并且在其他所有机房都装机正常。
2)千岛湖机房装机中会存在一定比例的yum装包不成功的问题,分析发现主要是千岛湖机房的yum源不稳定。这个模板也在实验室已经测试通过,并且在大部分机房都装机正常。
针对shell语言出在的以上问题,目前同事们有如下一些解决方案:
1)在run.sh中调另外一个run.py的python脚本,利用python这种高级语言的特性实现代码的可靠性。
2)在shell脚本的可能出问题的地方,增加条件判断。如下代码,会让代码量成倍增加。即使封装函数后,也会使代码增加不少。

df -h | grep  -Ew '/home/admin'
if [ $?-ne 0 ]; then
    echo "required dir not mounted"
    exit 1
fi

3)在check.sh增加检测项。出rpm包的检测项具备相对可操作性之外,其他很多地方不具可操作性。

shell信号捕获技术介绍

下面是一行捕获shell命令中返回非0返回码的代码。从这个定义行之后的行开始,每行命令执行后返回非0码会触发。触发后,将执行中间的echo输出。

trap 'echo "This shell command returns no 0 code"' ERR

以下是一个具体的例子

$ cat common.sh

#!/bin/bash
trap 'status=$?;echo "script $BASH_SOURCE is error";exit $status' ERR

$ cat file1.sh

#!/bin/bash
source $(dirname $0)/common.sh
./file2.sh

$ cat file2.sh

#!/bin/bash
source $(dirname $0)/common.sh
./file3.sh

$ cat file3.sh

#!/bin/bash
source $(dirname $0)/common.sh
ls file_is_not_exist
echo 'this line command will not be executed'

$./file1.sh

ls: cannot access file_is_not_exist: No such file or directory
script ./file3.sh is error
script ./file2.sh is error
script ./file1.sh is error

运行file1.sh脚本,file1.sh调用file2.sh,file2.sh调用file3.sh。file3.sh中会执行ls,显示一个不存在文件file_is_not_exist,此时ls file_is_not_exist 会返回一个非0返回值,触发 trap … ERR的信号捕获。进一步打印出了一个类似其他高级语言的错误栈的信息。并且,其后语句echo 'this line command will not be executed’将永远不会被执行,确保了脚本的严谨性和可靠性。

shell捕获技术除了可以捕获ERR伪信号之外,还可以捕获EXIT伪信号。下面是一行捕获shell整个脚本执行结束的代码。定义之后的。触发后,将执行中间的echo输出。

trap 'echo "there is the exit of shell script"' EXIT

使用shell信号捕获技术的实例介绍

以下是一个名为script_example的配置模板的目录结构,模板根目录是script_example 。其中clone.json和NEW_JSON两个文件是克隆模板核心分区部分,不在本文的说明范围。config.ini和common.sh文件是一些全局配置和函数定义部分。run.sh和post-install目录是分区后的更个性化的装机逻辑部分,其中post-install目录下的文件(例如initialize_directory.sh set_system_configuration.sh upgrade_kernel.sh)是对run.sh的展开。

$ cd clone_script_example/                         
$ ls 
clone.json  check.sh  common.sh  config.ini  NEW_JSON  post-install  run.sh     
$ ls post-install/
initialize_directory.sh  set_system_configuration.sh  upgrade_kernel.sh

config.ini文件定义了一些全局配置的信息,包括模板名称、post-install文件与执行顺序、其他一些用户自定义全局变量。

$ cat config.ini

# base param
clone_template='clone_script_example'                           # 克隆模板名称这里定义
scriptsdir="/usr/local/clonescripts/${clone_template}/post-install"
# post-install script list
postinstall_scripts='initialize_directory.sh upgrade_kernel.sh set_system_configuration.sh'   
 
# user defined param
lib_path_list='/usr/ali/bin /usr/ali/sbin /usr/ali/lib /usr/ali/lib64 /usr/ali/include'
 
# this is other user defined param
# other_vars=.......

functions.sh是系统的通用函数的函数定义文件,source进来可以在用户自定义的克隆模板中使用这些通用函数。
set -o errtrace 命令可以让 trap ‘command’ ERR信号捕获命令在当前生效的页面中的函数、子shell和命令替换中继承生效。
在shell的各种内置变量中,有如下几个在这里特别有用。
BASHSOURCE等价于 B A S H S O U R C E 等 价 于 BASH_SOURCE[0]表示执行命令的本脚本,BASHSOURCE[1]表示调用本脚本的脚本, B A S H S O U R C E [ 1 ] 表 示 调 用 本 脚 本 的 脚 本 , BASH_SOURCE[2]依次类推;LINENO表示代码在脚本中的实际行数位置; L I N E N O 表 示 代 码 在 脚 本 中 的 实 际 行 数 位 置 ; BASH_COMMAND表示当前执行的命令本身。
结合以上shell内置变量,可以将trap ‘command’ ERR语句完善的更加易用,当捕获到ERR信号后,打印出更加详细的信息。

$ cat common.sh 
#!/bin/bash 
source /usr/local/clonescripts/public/functions.sh
set -o errtrace
trap 'status=$?;echo "EXCEPTION @SCRIPT:$BASH_SOURCE,LINE:$LINENO; COMMAND:$BASH_COMMAND; STATUS:$status";exit $status' ERR
# Customed functions are defined:
function runmodule() {
    modulename=$1
    
    [ -f ${stagedir}/$modulename ] && chmod a+x ${scriptsdir}/$modulename
    
    echo "[RUN MODULE $1]"
    ${scriptsdir}/$modulename 
    echo "[FINISH MODULE $1]"
}
 

脚本中定义了一个log_exit_status函数,利用run.sh脚本退出时的伪信号EXIT的捕获,触发这个函数,从而将退出状态(正常或异常)保存到/tmp/clone_run_exit_status这个临时文件中。

$ cat run.sh

#!/bin/bash 
trap 'exit_status=$?;log_exit_status $exit_status' EXIT
source $(dirname $0)/config.ini
source $(dirname $0)/common.sh
 
function log_exit_status(){
    exit_status=$1
    echo $exit_status > /tmp/clone_run_exit_status
}
 
function main() {
    chmod a+x $0
 
    for s in `echo $postinstall_scripts`
    do
        runmodule $s
    done
    
}
 
main

initialize_directory.sh脚本负责初始化一些额外需要创建的目录。

$cat post-install/initialize_directory.sh

#!/bin/bash 
source $(dirname $0)/../config.ini
source $(dirname $0)/../common.sh
 
function main(){
    for dir in `echo $lib_path_list`;do
        [ -d ${dir} ] || mkdir -p ${dir}
    done
 
    # other directory initialize
    # mkdir ........
}
 
main

set_system_configuration.sh脚本负责设置os层面的个性化配置。

$ cat post-install/set_system_configuration.sh

#!/bin/bash 
source $(dirname $0)/../config.ini
source $(dirname $0)/../common.sh
 
function set_system() {
    echo "chmod 0644 /var/log/messages" >> /etc/rc.local
 
    # other system configuration set 
    # ......
}
 
main() {
    set_system
}
 
main

upgrade_kernel.sh负责设置kernel层面的个性化配置。

$ cat post-install/upgrade_kernel.sh

#!/bin/bash 
source $(dirname $0)/../config.ini
source $(dirname $0)/../common.sh
 
kernel_upgrade() {
    echo "kernel.pid_max = 262144" >> /etc/sysctl.conf
    # other kernel set
    # ......
}
 
main() {
    kernel_upgrade
}
 
main
 

check.sh是用于被克隆机器reboot后,用户自定义的检查机器克隆正确性的脚本。脚本中重定义了 trap ‘command’ ERR的信号捕获处理逻辑。当ERR信号被捕获时,会调用checkret函数,将有问题的检查项内容输出给iclone。并且会将get_run_exit_status函数作为第一个被检查的项,在这个函数中会读取被克隆机器重启之前run.sh执行的退出状态。
最后,如果所有check item检查项都成功,会通过捕获信号EXIT而触发check_exit_status函数,向iclone输出ALL Check OK的信息。

$cat check.sh

#!/bin/bash
 
source $(dirname $0)/config.ini
source $(dirname $0)/common.sh
trap 'exit_status=$?;check_exit_status $exit_status' EXIT
trap 'status=$?;checkret $status "$check_item";ret=1' ERR
 
function get_run_exit_status(){
    run_exit_status=`cat /tmp/clone_run_exit_status`
    run_exit_status_num=$((run_exit_status))
    return $run_exit_status_num
}
 
function checkret() {
    echo "$2 check FAILED"
}
 
function check_exit_status(){
    if [ $1 -eq 0 ]; then
        echo "ALL Check OK"
    fi
}
 
ret=0
chmod a+x $0
 
check_item='run.sh status'
get_run_exit_status
 
###################### there is the start of  customed check items ########################
 
check_item='Redhat releas'
grep 7.2 /etc/redhat-release
 
# check_item=......
# command ......
 
# check_item=......
# command ......
 
###################### there is the end of  customed check items ########################
 
exit $ret

信号捕获的屏蔽

以上脚本逻辑中,一旦信号捕获被设置后,将在脚本的全局生效。但是也有如下一些情况,需要屏蔽信号捕获。
需要获取标准输出信息时,例如 docker_info=(rpm−qa|grep−idocker),可以通过如下命令替换的方法屏蔽信号捕获。dockerinfo= ( r p m − q a | g r e p − i d o c k e r ) , 可 以 通 过 如 下 命 令 替 换 的 方 法 屏 蔽 信 号 捕 获 。 d o c k e r i n f o = (trap ‘exit 0’ ERR;rpm -qa | grep -i docker)。
不需要获取标准输出信息时,例如add_ali_public_net_route,可以通过如下子shell的方法屏蔽信号捕获。(trap ‘exit 0’ ERR;add_ali_public_net_route)。

模板重构效果

重构前代码

df -h | grep -Ew "/home/admin"
if [ $? -ne 0 ]; then
    echo "required dir not mounted"
    exit 1
fi

重构后代码

df -h | grep -Ew "/home/admin"

从以上对比可看到,代码将变得大为简洁和可靠。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北极象

如果觉得对您有帮助,鼓励一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值