AFAICT,continue
在 for 循环中调用另一个函数会破坏errexit
语义。在main()
函数中,如果函数中出现任何失败,我想继续进行下一次迭代build()
:
#! /usr/bin/env bash
export PS4='# ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]}() - [${SHLVL},${BASH_SUBSHELL},$?] '
set -o xtrace
set -o errexit
build() {
local _foo=$1
if [ "${_foo}" -eq 1 ]; then
false
fi
printf "%s with foo=%s builds ok\\n" "${FUNCNAME[0]}" "${_foo}"
}
main() {
for i in 1 2 3; do
build $i || continue
done
}
main "$@"
然而,在循环continue
内部for
会导致代码继续相反,在函数内部build()
,删除标志的效果errexit
:
$ ./foo.sh
# ./foo.sh:5: () - [3,0,0] set -o errexit
# ./foo.sh:23: () - [3,0,0] main
# ./foo.sh:18: main() - [3,0,0] for i in 1 2 3
# ./foo.sh:19: main() - [3,0,0] build 1
# ./foo.sh:8: build() - [3,0,0] local _foo=1
# ./foo.sh:10: build() - [3,0,0] '[' 1 -eq 1 ']'
# ./foo.sh:11: build() - [3,0,0] false
# ./foo.sh:14: build() - [3,0,1] printf '%s with foo=%s builds ok\n' build 1
build with foo=1 builds ok
# ./foo.sh:18: main() - [3,0,0] for i in 1 2 3
# ./foo.sh:19: main() - [3,0,0] build 2
# ./foo.sh:8: build() - [3,0,0] local _foo=2
# ./foo.sh:10: build() - [3,0,0] '[' 2 -eq 1 ']'
# ./foo.sh:14: build() - [3,0,0] printf '%s with foo=%s builds ok\n' build 2
build with foo=2 builds ok
# ./foo.sh:18: main() - [3,0,0] for i in 1 2 3
# ./foo.sh:19: main() - [3,0,0] build 3
# ./foo.sh:8: build() - [3,0,0] local _foo=3
# ./foo.sh:10: build() - [3,0,0] '[' 3 -eq 1 ']'
# ./foo.sh:14: build() - [3,0,0] printf '%s with foo=%s builds ok\n' build 3
build with foo=3 builds ok
正如您在带有 的行中看到的printf
,上一行的退出代码,false
确实是1
(前面括号内的第三个数字),因此它运行时就好像errexit
没有到位一样:
# ./foo.sh:14: build() - [3,0,1] printf '%s with foo=%s builds ok\n' build 1
我已经确认删除|| continue
会使 shell 在 时退出i=1
,因此errexit
被传递到 subhshell/函数。
任何帮助将非常感激。
版本
~ $ bash --version
GNU bash, version 5.0.3(1)-release (x86_64-pc-linux-gnu)
更新
关于以下问题有很多好的答案为什么这是。至于如何解决它,我发现这个解决方案是最简单的使脚本达到我想要的效果:将脚本更改为false
:
false || return $?
当然,缺点是我必须对函数调用的所有命令执行此操作。我可能不得不回到使用run()
包装器的旧方法,它执行传递的命令,检查它的返回代码并相应地使脚本失败。我想,做你期望errexit
做的事:-)
答案1
-e
这似乎与/-errexit
中的描述相符bash 文档:
如果失败的命令是紧跟在 while 或until 关键字之后的命令列表的一部分(if 语句中测试的一部分),则 shell 不会退出,在 && 或 || 中执行的任何命令的一部分列表除了最后一个 && 或 || 后面的命令之外,管道中除最后一个命令之外的任何命令,或者命令的返回状态与 ! 反转。
[...]如果复合命令或 shell 函数在忽略 -e 的上下文中执行,则复合命令或函数体内执行的任何命令都不会受到 -e 设置的影响,即使设置了 -e 并且命令返回故障状态。
> My initial gripe about errexit (and its man page description) is that the
> following doesn't behave as a newbie would expect it to:
>
> set -e
> f() {
> false
> echo "NO!!"
> }
> f || { echo "f failed" >&2; exit 1; }
Indeed, the correct behavior mandated by POSIX (namely, that 'set -e' is
completely ignored for the duration of the entire body of f(), because f
was invoked in a context that ignores 'set -e') is not intuitive. But
it is standardized, so we have to live with it.
这POSIX的描述-e
说:
-e
当此选项打开时,如果一个简单命令由于 Shell 错误的后果中列出的任何原因而失败,或者返回退出状态值 >0,并且不是 while、until 或 if 关键字后面的复合列表的一部分, 和不是 AND 或 OR 列表的一部分,并且不是前面带有 ! 的管道。保留字,则 shell 应立即退出。
答案2
来自手动的[强调我的]:
errexit
与...一样
-e
。
-e
如果管道 […](可能由单个简单命令 […]、列表 […] 或复合命令 […] 组成)返回非零状态,请立即退出。如果失败的命令是 [...]
&&
或列表中执行的任何命令的一部分(最后一个或||
后面的命令除外),则 shell 不会退出&&
||
,[…][…]
如果 [...] shell 函数在被忽略的上下文中执行
-e
,则在 [...] 函数体内执行的任何命令都不会受到该-e
设置的影响,即使-e
已设置并且命令返回失败状态。[…]
您的shell 函数在被忽略的build $i || continue
build
上下文中执行。是在函数体内执行的命令,它不受设置的影响,因此它甚至不会阻止运行。-e
false
-e
printf
删除|| continue
并调用只是build $i
将函数的每个部分放在一个上下文中,-e
其中不是被忽略,因此整个代码因false
和 之后退出false
(没有到达printf
)。
这似乎errexit
是一个全局设置(当不被忽略时)终止整个脚本。人们不能(或至少不能轻易地)让它终止一个函数,但不能终止整个脚本。