使用if表达式时,bash中的`set -e`无法正常工作?

4

看一下这个小脚本:

#!/bin/bash

function do_something() {(
    set -e

    mkdir "/opt/some_folder"                                     # <== returns 1 -> abort?
    echo "mkdir returned $?"                                     # <== sets $0 to 0 again

    rsync $( readlink -f "${BASH_SOURCE[0]}" ) /opt/some_folder/ # <== returns 23 -> abort?
    echo "rsync returned $?"                                     # <== sets $0 to 0 again
)}


# here  every command inside `do_something` will be executed - regardless of errors
echo "run do_something in if-context.."
if ! do_something ; then
  echo "running do_something did not work"
fi

# here `do_something` aborts on first error
echo "run do_something standalone.."
do_something
echo $?

我尝试按照这里的建议去做(不要错过引入子shell的额外括号),但我没有单独执行函数(在我的情况下是do_something),而是与if表达式一起执行。
现在,当我运行if ! do_something时,set -e命令似乎没有效果。
有人能向我解释一下吗?

除了它被Simon Doppler解释为这样的方式之外,如果-e也会在if内部中止,那么这将是适得其反的。请注意,if foo; then ....; else ....; fi的语义是执行_then_部分,如果foo返回零退出代码,则执行_else_部分,如果它返回非零退出代码。如果set -e甚至在这样调用foo时中止脚本,则else部分永远没有机会被执行。 - user1934428
2个回答

6

这是在Bash参考手册中预期并描述的。

-e

[...] 如果失败的命令是紧随while或until关键字的命令列表的一部分,或者是if语句中的测试的一部分,则shell不会退出。[...]

[...]

如果复合命令或shell函数在忽略-e的情况下执行,则复合命令或函数体内执行的所有命令都不会受到-e设置的影响,即使-e被设置且命令返回失败状态。如果复合命令或shell函数在忽略-e的情况下执行时设置了-e,则该设置直到完成复合命令或包含函数调用的命令后才会生效。


有关奇怪的 set -e 行为的更多示例,请参见 BashFAQ#105:为什么 set -e(或 set -o errexit,或 trap ERR)没有做我期望的事情? - Gordon Davisson

0
使用函数来更改设置和陷阱可以克服这个限制,至少在Homebrew Bash 5.2.15(1)中。
如果我从这里开始:
errexit_ignore()   {
    set +e
    trap -      ERR
}
errexit_fail() {
    set -e
    trap failed ERR
}
errexit_fail

我以后可以做出像这样可怕而有用的事情:

for customization in ${customizations[@]}; do
    log_mark 2 "Checking ${customization} ..."
    errexit_ignore
    diff -qs "${expected}/${customization}" "${tmpdir}/${customization}"
    diff_exitcode="${?}"
    errexit_fail
    if [ "${diff_exitcode}" != "0" ]; then  ##  If it's not the expected file, the customization may have already been applied
        errexit_ignore
        diff -qs "${tmpdir}/${customization}" "${customized}/${customization}"
        diff_exitcode="${?}"
        errexit_fail
        if [ "${diff_exitcode}" != "0" ]; then  ##  If it's neither the expected file nor the customized file, either default has changed or the customization has changed
            errexit_ignore
            git ls-files --error-unmatch "${customized}/${customization}"
            track_exitcode="${?}"  ##  Detect untracked file
            git diff --exit-code "${customized}/${customization}"
            diff_exitcode="${?}"  ##  Detect modified tracked file
            errexit_fail
            if [ "${track_exitcode}" != "0" -o "${diff_exitcode}" != "0" ]; then  ##  If the customization has uncomitted changes, assume the default hasn't changed
                log_mark 1 "Customized ${customization} will be updated"
            else  ##  If the default has changed, manual review is needed (which may result in an updated customization)
                diff -u "${expected}/${customization}" "${tmpdir}/${customization}" || :
                diff -u "${tmpdir}/${customization}" "${customized}/${customization}" || :
                abort "Default version of ${customization} has changed, expected version must be updated and customization must be checked for compatibility"
            fi
        else
            log_mark 1 "Customized ${customization} already in place"
        fi
    else
        log_mark 1 "Default ${customization} has not changed"
    fi
done

注:

  • trap function ERR 之后,除非你也执行 trap - ERR,否则 set +e 不起作用
  • 无论是 errexit_ignore 还是 errexit_fail 都不能在单行上定义(我不确定为什么)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接