当子 shell 失败时,为什么 bash 标志 -e 不退出?

问题描述 投票:0回答:4

我在这里有点困惑。我的目标是当脚本中的任何命令失败时,让 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
4个回答
17
投票

这似乎与您的

bash
版本有关。在我可以访问的机器上,bash 版本 3.1.17 和 3.2.39 表现出这种行为,bash 4.1.5 则没有。

虽然有点难看,但在两个版本中都有效的解决方案可能是这样的:

#!/bin/bash -e

(false) || exit $?

echo $?
echo "Line reached!"

bash 源代码变更日志中有一些注释与

set -e
选项的错误相关。


17
投票

-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;

4
投票

我在 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

0
投票

我想要 (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
© www.soinside.com 2019 - 2024. All rights reserved.