如何在shell函数中获得效果并使用"set -e"?

Rob*_*sak 32 shell sh

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, untilif关键字,并且不是AND或者OR列表的一部分,并且不是以!保留字开头的管道,那么shell应该立即退出.

在您的情况下,false是一个管道的一部分前面! 部分if.因此解决方案是重写代码,使其不重写.

换句话说,这里的功能没什么特别之处.尝试:

set -e
! { false; echo hi; }
Run Code Online (Sandbox Code Playgroud)

  • 这解释了行为 - 谢谢 - 但它并没有真正解决我的问题.如何获取`set -e`的_behaviour_,但是本地化为函数?我想编写我的函数而不关心单个简单命令是成功还是失败,并且只要这些简单命令中的任何一个失败,函数就立即返回非零(大概是`$?`).正如Gintautas在另一个答案中指出的那样,与'&&'结合会做到这一点,但我想要的是,为了方便起见,我不需要改变函数的主体. (3认同)
  • 换句话说,`set -e`是一个繁琐的野兽,并且通常不是你想要你的脚本做什么,即使它看起来像一个非常有用的锤子. (3认同)
  • “有”一条管道,尽管是一种简陋的管道。如果您检查标准(特别是“2.10.2 Shell Grammar Rules”),您将看到管道至少包含一个命令(可能很简单,是 func.def 的复合),前面有可选的 bang 符号。除了管道之外,没有其他方法可以引入否定。 (2认同)
  • @RobieBasak我很感激你为什么这么问,但你的要求并没有多大意义.`set -e`*确实*适用于函数的内容,*除非*函数以`set -e`中排除的方式之一调用,如Roman链接到的文档中所述.`set -e`只适用于某些调用上下文,所以如果你想让你的函数在出现问题时立即返回*无论什么*追加`|| 返回每个命令(或用`&&`链接所有内容)是标准解决方案. (2认同)

小智 11

您可以直接使用子shell作为函数定义,并将其设置为立即退出set -e.这将仅限制set -e函数subshel​​l 的范围,并且稍后将避免在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)

  • 我知道这是一个旧答案,但这在 Bash 4.4 和 Bash 5 中对我不起作用。我得到“Should NOT get HERE”输出。我的输出是 `++ echo a ++ false ++ echo Should NOT get HERE ++ exit 0 ` 这对您仍然有效吗? (2认同)

ant*_*tak 9

我最终选择了这个,显然有效.我首先尝试了导出方法,但后来发现我需要导出脚本使用的每个全局(常量)变量.

禁用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. 此代码也不能出现在任何上下文中,因此如果您在函数中使用此代码,则调用者必须使用相同的subshel​​l/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将起作用.

  • 注意:当这是再次与`if`、`||`、`&&`等一起使用的函数的一部分时,这将不起作用:( (2认同)

Wil*_*ell 7

这有点像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
  '
}


jra*_*ali 7

注意/编辑:正如评论者指出的那样,这个答案使用bash, 而不像sh他的问题中使用的OP。当我最初发布答案时,我错过了这个细节。无论如何,我都会保留这个答案,因为某些路人可能会对它感兴趣。

\n

你准备好了吗?

\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"\n
Run Code Online (Sandbox Code Playgroud)\n

和输出:

\n
the 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\n
Run Code Online (Sandbox Code Playgroud)\n

我还没有在野外测试过这个,但我的脑海中浮现出很多优点:

\n
    \n
  1. 其实没那么慢。我在使用和不使用该选项的情况下都在紧密循环中运行了该脚本functrace,并且在 10 000 次迭代下时间彼此非常接近。

    \n
  2. \n
  3. 您可以扩展此 DEBUG 陷阱来打印堆栈跟踪,而无需对 $FUNCNAME 和 $BASH_LINENO 进行整个循环。你有点免费得到它(除了实际做回声线)。

    \n
  4. \n
  5. 不必担心这个shopt -s inherit_errexit问题。

    \n
  6. \n
\n


Gin*_*kas 5

使用运算符连接函数中的所有命令&&。这并不是太麻烦,并且会给出您想要的结果。