set -e(或以脚本开头#!/bin/sh -e)对于在出现问题时自动轰炸非常有用.它使我不必错误地检查可能失败的每个命令.
如何在函数内获得相应的内容?
例如,我有以下脚本在出错时立即退出并出现错误退出状态:
#!/bin/sh -e
echo "the following command could fail:"
false
echo "this is after the command that fails"
Run Code Online (Sandbox Code Playgroud)
输出如预期:
the following command could fail:
Run Code Online (Sandbox Code Playgroud)
现在我想把它包装成一个函数:
#!/bin/sh -e
my_function() {
echo "the following command could fail:"
false
echo "this is after the command that fails"
}
if ! my_function; then
echo "dealing with the problem"
fi
echo "run this all the time regardless of the success of my_function"
Run Code Online (Sandbox Code Playgroud)
预期产量:
the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function
Run Code Online (Sandbox Code Playgroud)
实际产量:
the following output could fail:
this is after the command that fails
run this all the time regardless of the success of my_function
Run Code Online (Sandbox Code Playgroud)
(即函数忽略set -e)
这可能是预期的行为.我的问题是:如何set -e在shell函数中获得效果并使用它?我希望能够设置一些东西,以便我不必单独检查每个调用,但脚本将在遇到错误时停止.它应该尽可能地展开堆栈直到我检查结果,或者如果我没有检查它就退出脚本本身.这set -e已经是什么了,除了它没有嵌套.
我在Stack Overflow之外发现了同样的问题,但没有合适的答案.
Rom*_*aka 15
来自以下文件set -e:
当这个选项是,如果一个简单的命令用于任何的在壳错误的后果列出的原因失败或返回一个退出状态值> 0,并且不是化合物列表的一部分的下面
while,until或if关键字,并且不是AND或者OR列表的一部分,并且不是以!保留字开头的管道,那么shell应该立即退出.
在您的情况下,false是一个管道的一部分前面! 和部分if.因此解决方案是重写代码,使其不重写.
换句话说,这里的功能没什么特别之处.尝试:
set -e
! { false; echo hi; }
Run Code Online (Sandbox Code Playgroud)
小智 11
您可以直接使用子shell作为函数定义,并将其设置为立即退出set -e.这将仅限制set -e函数subshell 的范围,并且稍后将避免在set +e和之间切换set -e.
此外,您可以在if测试中使用变量赋值,然后在另一个else语句中回显结果.
# use subshell for function definition
f() (
set -exo pipefail
echo a
false
echo Should NOT get HERE
exit 0
)
# next line also works for non-subshell function given by agsamek above
#if ret="$( set -e && f )" ; then
if ret="$( f )" ; then
true
else
echo "$ret"
fi
# prints
# ++ echo a
# ++ false
# a
Run Code Online (Sandbox Code Playgroud)
我最终选择了这个,显然有效.我首先尝试了导出方法,但后来发现我需要导出脚本使用的每个全局(常量)变量.
禁用set -e,然后在已set -e启用的子shell中运行函数调用.将子shell的退出状态保存在变量中,重新启用set -e,然后测试var.
f() { echo "a"; false; echo "Should NOT get HERE"; }
# Don't pipe the subshell into anything or we won't be able to see its exit status
set +e ; ( set -e; f ) ; err_status=$?
set -e
## cleaner syntax which POSIX sh doesn't support. Use bash/zsh/ksh/other fancy shells
if ((err_status)) ; then
echo "f returned false: $err_status"
fi
## POSIX-sh features only (e.g. dash, /bin/sh)
if test "$err_status" -ne 0 ; then
echo "f returned false: $err_status"
fi
echo "always print this"
Run Code Online (Sandbox Code Playgroud)
您不能运行f作为管道的一部分,或作为一部分&&的||命令列表(除非在管道或列表中的最后一个命令),或在一个条件if或者while,或者说忽略其它环境中set -e. 此代码也不能出现在任何上下文中,因此如果您在函数中使用此代码,则调用者必须使用相同的subshell/save-exit-status技巧.set -e考虑到限制和难以阅读的语法,这种类似于抛出/捕获异常的语义的使用并不适合一般使用.
trap err_handler_function ERR具有相同的限制set -e,因为它不会在set -e不能在失败的命令上退出的上下文中触发错误.
您可能认为以下内容可行,但它不会:
if ! ( set -e; f );then ##### doesn't work, f runs ignoring -e
echo "f returned false: $?"
fi
Run Code Online (Sandbox Code Playgroud)
set -e在子shell中没有生效,因为它记得它在一个条件下if.我认为作为子shell会改变它,但只是在一个单独的文件中并在其上运行一个完整的单独的shell将起作用.
这有点像kludge,但你可以这样做:
export -f f if sh -ec f; then ...
如果你的shell支持export -f(bash),这将有效.
请注意,这不会终止脚本.f中的false之后的echo将不会执行,if之后也将执行if之后的语句.
如果您使用的shell不支持export -f,则可以通过在函数中运行sh来获取所需的语义:
f() { sh -ec '
echo This will execute
false
echo This will not
'
}
注意/编辑:正如评论者指出的那样,这个答案使用bash, 而不像sh他的问题中使用的OP。当我最初发布答案时,我错过了这个细节。无论如何,我都会保留这个答案,因为某些路人可能会对它感兴趣。
你准备好了吗?
\n这是一种利用 DEBUG 陷阱来实现此目的的方法,该陷阱在每个命令之前运行,并且会产生类似于其他语言中的整个异常/try/catch 习惯用法的错误。看一看。我已经让你的例子又加深了一次“调用”。
\n#!/bin/bash\n\n# Get rid of that disgusting set -e. We don't need it anymore!\n# functrace allows RETURN and DEBUG traps to be inherited by each\n# subshell and function. Plus, it doesn't suffer from that horrible\n# erasure problem that -e and -E suffer from when the command \n# is used in a conditional expression.\nset -o functrace\n\n# A trap to bubble up the error unless our magic command is encountered \n# ('catch=$?' in this case) at which point it stops. Also don't try to\n# bubble the error if were not in a function.\ntrap '{ \n code=$?\n if [[ $code != 0 ]] && [[ $BASH_COMMAND != '\\''catch=$?'\\'' ]]; then\n # If were in a function, return, else exit.\n [[ $FUNCNAME ]] && return $code || exit $code\n fi\n}' DEBUG\n\nmy_function() {\n my_function2\n}\n\nmy_function2() {\n echo "the following command could fail:"\n false\n echo "this is after the command that fails"\n}\n\n# the || isn't necessary, but the 'catch=$?' is.\nmy_function || catch=$?\necho "Dealing with the problem with errcode=$catch (\xe2\x8c\x90\xe2\x96\xa0_\xe2\x96\xa0)"\n\necho "run this all the time regardless of the success of my_function"\nRun Code Online (Sandbox Code Playgroud)\n和输出:
\nthe following command could fail:\nDealing with the problem with errcode=1 (\xe2\x8c\x90\xe2\x96\xa0_\xe2\x96\xa0)\nrun this all the time regardless of the success of my_function\nRun Code Online (Sandbox Code Playgroud)\n我还没有在野外测试过这个,但我的脑海中浮现出很多优点:
\n其实没那么慢。我在使用和不使用该选项的情况下都在紧密循环中运行了该脚本functrace,并且在 10 000 次迭代下时间彼此非常接近。
您可以扩展此 DEBUG 陷阱来打印堆栈跟踪,而无需对 $FUNCNAME 和 $BASH_LINENO 进行整个循环。你有点免费得到它(除了实际做回声线)。
\n不必担心这个shopt -s inherit_errexit问题。