让 shell 脚本在关键语句出错时终止执行

原帖:http://zhangbin.cc/2011/04/23/shell-exit-on-error/


最近写了一个 shell 脚本, 里面有类似这样的语句:

cd $SOME_PLACE
mv * $ANOTHER_PLACE

有一次运行的时候, $SOME_PLACE 没有创建成功, 因此 cd 失败, 但 mv 仍然被执行了, 结果就是所有脚本文件都被移动到了不该去的地方. ls 的结果空荡荡的, 吓出一身冷汗. 之后就想, 要是某些关键的语句没有执行成功的话能记个日志自动终止脚本的执行就好了. 于是很自然的就写了这样一个函数:

function _do()
{
        $@ || ( alert "exec failed: $@"; exit -1; )
}

_do statement # alert and exit when failed

alert 是另一个函数, 作用就是记录日志并发送报警短信把熟睡的程序员叫醒. 在脚本的关键语句前面加上 _do, 就能在出错时报警并终止整个脚本执行.

这个函数实现乍一看貌似没什么问题, 其实埋下了新的陷阱.

陷阱一: statement 出错时, 报警了, 可脚本没有像预期那样退出. 原因出在括号上:

(list) list is executed in a subshell environment

因此, 括号里的 exit 只是退出了 subshell 而已, 对上层 shell (脚本) 没有影响. 换成大括号就可以了:

{ list; } list  is simply executed in the current shell environment.

陷阱二: 若 statement 中的参数包含带空格的字符串, 例如 _do cat "a b" ("a b" 是一个已存在的文件), 那么实际的行为会变成 cat a b. 解决方案: 把函数中的 $@ 用双引号包起来.

@: Expands to the positional parameters, starting from one.  When the expansion occurs within  double  quotes, each  parameter  expands  to a separate word.  That is, "$@" is equivalent to "$1" "$2" ...

于是函数变成了这个样子:

function _do()
{
        "$@" || { alert "exec failed: ""$@"; exit -1; }
}

陷阱三: statement 中包含管道操作的话, 如果写成 _do statementA | statementB 或者 statementA | _do statementB 都只对后面的一个 statement 有效, 且出错时不能终止脚本的执行, 原因是管道的两端都在各自的 subshell 中, exit 仅影响 subshell. 而 _do ( statementA | statementB ) 或者_do { statementA | statementB } 都会报语法错误. 无奈, 只好又写了一个函数, 单独处理带管道的情形:

function _do_ex()
{
        eval "$1" || { alert "exec failed: ""$1"; exit -1; }
}

_do_ex "statementA | statementB"

PS:

其实一般情况下, 只要在脚本中打开 set -e, 就能在遇到错误时终止脚本的执行. 但这样一来不便于选择性的进行错误控制 (脚本中部分语句允许执行失败), 也不方便在出错时加上自己的处理逻辑 (日志, 报警).

另外, 报警时可以加上出错的行号, 方法可参考 solrex 的这篇博客.

对于篇幅不长的脚本, 也可以使用 cd $SOME_PLACE && mv * $ANOTHER_PLACE || exit -1 类似的方法在出错时终止脚本的执行.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值