【动机】
众所周知, bash 选项 errexit(默认为 off) 如果被设置为 on, 意味着只要主进程中任何一个函数(或命令)调用的退出码 (exitcode) 非0, 程序马上退出, 这对提高编码的健壮性的作用不言而喻.
例如, 下面的代码:
maybe_error_fun(){
echo "This function maybe throw an error..."
return 1
}
motive(){
echo "step 1....."
maybe_error_fun
echo "step 2...."
}
set -e
motive
执行结果:
step 1.....
This function maybe throw an error...
motive 函数的第二句 echo “step 2…” 没有执行. 但是事情总有两面性, 如果我们想忽略 maybe_error_fun 的错误, 让它不影响后面的语句执行, 又应该怎么办呢?
最简单的方法, 是在 maybe_error_fun 调用上稍微做点手脚, 写成:
maybe_error_fun || :
可达目的. 可是这仅仅适用于 errexit 选项, 本文讨论的是如何通过临时修改选项然后还原达到目的.
考虑到 motive 函数, 不能依赖, 同时也要负责任的还原 shell 的 errexit 的配置, 利用 bash 选项的获取与设置, 修改 motive 函数, 得到下面的代码:
motive1(){
echo "step 1....."
local orig_ee
tstatx -t errexit -n orig_ee
tstatx -t errexit -v off # 或者 set +e
maybe_error_fun
echo "step 2...."
tstatx -t errexit -v $orig_ee
maybe_error_fun
echo "step 3..."
}
执行结果:
step 1.....
This function maybe throw an error...
step 2....
This function maybe throw an error...
打印 step 2…, 说明修改成功, 而不打印 step 3…, 说明还原成功. 该方案可用, 似乎不够优雅.
【意图】
结合栈的实现, 以及 bash 选项的获取与设置, 封装选项的修改和还原.
【实现】
在第四篇的 bash 选项的获取与设置的 options.sh 中, 添加函数 pusht, 用于将指定选项入栈, 并支持一次入栈多个选项:
:<<COMMENT
将一个或多个 Option 的状态值推入栈
$*: 指定的 option 名称列表
usage: pusht <opt1> <opt2> ...
返回值: 无
COMMENT
pusht(){
local opt curr
for opt in "$@";do
tstatx -t $opt -n curr
push $curr
done
}
出栈函数 popt, 也支持多个选项, 注意顺序应与 pusht 的选项相反:
:<<COMMENT
将栈中数据, 依次弹出, 并依次赋值给一个或多个 option
$*: 指定的 option 名称列表
usage: popt <opt1> <opt2> ...
返回值: 无
COMMENT
popt(){
local opt value
for opt in "$@";do
# 空值检查的原因, 在于赋值给 value, 则 tstatx 调用因为缺少 -v
# 导致设置变为获取.
peek value
if [ "$value" ];then
tstatx -t $opt -v "$value"
etop
fi
done
}
由于 errexit 的修改还原操作是如此频繁, 为此专门创建函数 pushee / popee:
:<<COMMENT
将 errexit 的状态值推入栈
usage: pushee
返回值: 无
COMMENT
pushee(){
pusht errexit
}
:<<COMMENT
将栈中数据, 弹出到 errexit
usage: popee
返回值: 无
COMMENT
popee(){
popt errexit
}
【测试】
新建文件 test.sh, 先测试 pusht / popt
# 根据实际文件位置修改路径
source ./stack.sh
source ./options
way1(){
check(){
echo -e "\t\tcheck step $1..."
shopt -o -p allexport
shopt -o -p hashall
shopt -p checkjobs
shopt -p checkwinsize
}
check 1
pusht allexport hashall checkjobs checkwinsize
echo -e "\t\tafter pusht, stack is <$(stack_status)>"
echo "now modify..."
shopt -o -s allexport # off --> on
shopt -o -u hashall # on --> off
shopt -s checkjobs # off --> on
shopt -u checkwinsize # on --> off
check 2
popt checkwinsize checkjobs hashall allexport
echo -e "\t\tafter popt, stack is <$(stack_status)>"
check 3
}
clear
way1
测试结果如下:
check step 1...
set +o allexport
set -o hashall
shopt -u checkjobs
shopt -s checkwinsize
after pusht, stack is <[0]=(off) [1]=(on) [2]=(off) [3]=(on)>
now modify...
check step 2...
set -o allexport
set +o hashall
shopt -s checkjobs
shopt -u checkwinsize
after popt, stack is <>
check step 3...
set +o allexport
set -o hashall
shopt -u checkjobs
shopt -s checkwinsize
下面测试 pushee / popee:
# 根据实际文件位置修改路径
source ./stack.sh
source ./options
way2(){
echo "way2 function start..."
pushee
set +e
local cmd="no-such-command"
echo "执行未知命令将异常, 但由于 set +e 临时取消 "错误立即退出的行为" "
$cmd
echo "way2 funciton continue."
popee
echo "使用 popee 恢复 errexit 后,再次执行未知命令,将立即退出。
说明 popee 生效, "
$cmd
echo "这句由于最初设置的 set -e 已经恢复, 而不会运行."
}
clear
set -e
way2
结果如下:
way2 function start...
执行未知命令将异常, 但由于 set +e 临时取消 错误立即退出的行为
/mnt/d/Estate/asset/OS/linux/application/blogging/lib/unit_test.sh: line 671: no-such-command: command not found
way2 funciton continue.
使用 popee 恢复 errexit 后,再次执行未知命令,将立即退出。
说明 popee 生效,
/mnt/d/Estate/asset/OS/linux/application/blogging/lib/unit_test.sh: line 677: no-such-command: command not found
成功.