我在这里有点困惑。我的目标是当脚本中的任何命令失败时,让 bash 脚本以非零退出代码退出。使用 -e 标志,我认为即使使用子 shell 也会出现这种情况。下面是一个简化的例子:
#!/bin/bash -e
(false)
echo $?
echo "Line reached!"
这是运行时的输出:
[$]>Tests/Exec/continuous-integration.sh
1
Line reached!
Bash 版本:CentOS 上的 3.2.25
这似乎与您的
bash
版本有关。在我可以访问的机器上,bash 版本 3.1.17 和 3.2.39 表现出这种行为,bash 4.1.5 则没有。
虽然有点难看,但在两个版本中都有效的解决方案可能是这样的:
#!/bin/bash -e
(false) || exit $?
echo $?
echo "Line reached!"
bash 源代码变更日志中有一些注释与
set -e
选项的错误相关。
-e
选项适用于当前shell,对于子shell、函数等,我们使用-E
表格男子猛击
-E 如果设置,则 ERR 上的任何陷阱都会由 shell 函数、命令替换和在子 shell 环境中执行的命令继承。
对于高级用户,我们有一种严格的模式:
# one line
set -Eeuo pipefail
-E
上面已经解释了-e
立即退出-u
找到未绑定变量退出-o
设置选项pipefail
如果管道出现故障,请退出如果你想使用严格模式作为函数
################################################################################
#
# bash strict mode
#
################################################################################
strict_mode(){
set -T # inherit DEBUG and RETURN trap for functions
set -C # prevent file overwrite by > &> <>
set -E # inherit -e
set -e # exit immediately on errors
set -u # exit on not assigned variables
set -o pipefail # exit on pipe failure
}
strict_mode;
我在 El Capitan 之前的 SuSE 11.3 和 Mac OS 上的 bash 版本 3.2.51 中看到过这种行为。 El Capitan 上的 Bash 3.2.57 具有“正确”行为,即像 bash 4 一样。
但是,上面提出的解决方法是添加“|| exit $?”在子 shell 的结束括号之后,无论 bash 的版本如何,都违背了 -e 标志的意图。来自男人狂欢:
-e 如果简单命令(参见上面的 SHELL GRAMMAR)退出,则立即退出 具有非零状态。如果失败的命令是 shell,则 shell 不会退出 命令列表的一部分紧跟在 while 或 Until 关键字之后,部分 if 语句中测试的一部分,&& 或 || 的一部分列表,...
子 shell 后跟“|| exit $?”显然算作命令列表;并且 bash -e 标志不适用于子 shell 内的任何命令。尝试一下:
$ set -e
$ ( echo before the error; false; echo after the error, status $?; ) || echo after the subshell, status $?
before the error
after the error, status 1
$
因为子 shell 后面跟着 ||,所以即使使用 set -e,也会运行“错误后回显”。不仅如此,子 shell 退出 0,因为“echo”运行了。那么“||退出$?”甚至不会运行“退出”。可能不是我们想要的!
据我所知,以下公式与 bash 版本兼容,无论它们是否在 subshell 之后支持 bash -e。如果 -e 标志恰好被重置,它甚至可以正确运行:
在 bash 脚本中每个子 shell 的右括号后立即添加以下行:
case $?/$- in ( 0/* ) ;; ( */*e* ) exit $? ;; esac # honor bash -e flag when subshell returns
我想要 (1) 在子 shell 中捕获错误; (2) 发生这种情况时停止运行主脚本; (3)可完成该批次中所有好的子壳。解决办法如下:
#!/bin/bash
set -Eeuo pipefail
N=6
STEP=3
function traperr
{
echo " ERROR TRAPPED at line $1"
kill 0 # kill the master shell and all subshells
}
trap 'traperr $LINENO' ERR
# this variant seems to work properly: the master terminates
# after finishing all subshells and catching an error in a subshell
# (does not print "test end")
#
echo " test start"
pids=
for i in `seq 1 $N`
do
(
echo -n " i=$i begin, PID = $BASHPID"
if ((i % STEP))
then
echo " (good)"
else
echo " (error)"
fi
sleep 10
if ((i % STEP))
then
a=$i
else
a=$b # error!
fi
echo " i=$i end"
)&
pids+=($!)
sleep 1
done
wait ${pids[@]}
echo " test end"
这会产生想要的输出:
test start
i=1 begin, PID = 2819980 (good)
i=2 begin, PID = 2820263 (good)
i=3 begin, PID = 2820281 (error)
i=4 begin, PID = 2820438 (good)
i=5 begin, PID = 2820517 (good)
i=6 begin, PID = 2820638 (error)
i=1 end
i=2 end
./test.sh: line 35: b: unbound variable
i=4 end
i=5 end
./test.sh: line 35: b: unbound variable
ERROR TRAPPED at line 42
Terminated