我如何获得“的效果和有用性?”;集合-e";在shell函数中?

我如何获得“的效果和有用性?”;集合-e";在shell函数中?,shell,error-handling,sh,Shell,Error Handling,Sh,set-e(或以#!/bin/sh-e开头的脚本)对于在出现问题时自动弹出非常有用。这样我就不用对可能失败的每个命令进行错误检查了 如何在一个函数中得到它的等价物 例如,我有以下脚本,在出现错误时立即退出,并具有错误退出状态: #!/bin/sh -e echo "the following command could fail:" false echo "this is after the command that fails" 产出如预期: the following command c

set-e
(或以
#!/bin/sh-e开头的脚本)对于在出现问题时自动弹出非常有用。这样我就不用对可能失败的每个命令进行错误检查了

如何在一个函数中得到它的等价物

例如,我有以下脚本,在出现错误时立即退出,并具有错误退出状态:

#!/bin/sh -e

echo "the following command could fail:"
false
echo "this is after the command that fails"
产出如预期:

the following command could fail:
现在我想把它包装成一个函数:

#!/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"
f() { sh -ec ' echo This will execute false echo This will not ' } 预期产出:

the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function
实际产量:

the following output could fail:
this is after the command that fails
run this all the time regardless of the success of my_function
(即,该函数正在忽略
set-e

这大概是预期的行为。我的问题是:如何在shell函数中获得
set-e
的效果和有用性?我希望能够设置一些东西,这样我就不必逐个检查每个调用的错误,但是脚本在遇到错误时将停止。它应该根据需要将堆栈展开,直到我检查结果为止,或者如果我没有检查,则退出脚本本身。这是
set-e
已经做过的,只是它没有嵌套


我发现堆栈溢出之外有问题,但没有合适的答案。

使用
&&
操作符将函数中的所有命令连接起来。这不会太麻烦,会给你想要的结果。

我知道这不是你所要求的,但你可能知道也可能不知道你所追求的行为是建立在“制造”中的。“make”进程中任何失败的部分都会中止运行。不过,这是一种与shell脚本完全不同的“编程”方式。

来自
set-e
的文档:

启用此选项时,如果对任何 原因列在事故后果表中 Shell错误或返回退出状态 值>0,并且不属于 在
while
之后的复合列表,
直到
,或
如果
关键字,并且不是 属于
列表的一部分,并且不是 前面有
的管道保留
一句话,那么外壳就马上
退出

在您的例子中,
false
是管道的一部分,前面有
的一部分(如果
)。因此,解决方案是重写代码,使其不受影响

换句话说,函数在这里没有什么特别之处。尝试:

set -e
! { false; echo hi; }

您需要在子shell(括号内)中调用函数来实现这一点

我想你应该这样写剧本:

#!/bin/sh -e

my_function() {
    echo "the following command could fail:"
    false
    echo "this is after the command that fails"
}

(my_function)

if [ $? -ne 0 ] ; then
    echo "dealing with the problem"
fi

echo "run this all the time regardless of the success of my_function"
然后输出为(根据需要):


这有点困难,但您可以:

export -f f if sh -ec f; then ... 出口-f 如果sh-ecf;然后 ... 如果您的shell支持export-f(bash支持)

请注意,这不会终止脚本。回声 f中的false之后将不会执行,主体也不会执行 但将执行if之后的语句

如果您使用的shell不支持export-f,则可以 通过在函数中运行sh获得所需的语义:

#!/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"
f() { sh -ec ' echo This will execute false echo This will not ' } f(){sh-ec' 这将执行 假的 呼应这一点是不会的 ' }
我最终选择了这个,这显然是可行的。起初我尝试了导出方法,但后来发现我需要导出脚本使用的每个全局(常量)变量

禁用
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"
不能将
f
作为管道的一部分,或作为
|
命令列表的
&
的一部分运行(管道或列表中的最后一个命令除外),或作为
if
while
中的条件运行,或作为忽略
set-e
的其他上下文运行此代码也不能在这些上下文中使用,因此如果在函数中使用此代码,调用方必须使用相同的子shell/保存退出状态欺骗。鉴于限制和难以阅读的语法,将
set-e
用于类似于抛出/捕获异常的语义并不真正适合于一般使用

trap err\u handler\u function err
set-e
具有相同的限制,即在命令失败时
set-e
不会退出的上下文中,它不会因错误而触发

您可能认为以下方法可行,但事实并非如此:

if ! ( set -e; f );then    ##### doesn't work, f runs ignoring -e
    echo "f returned false: $?"
fi

set-e
不会在子shell内生效,因为它记住它在
if
的条件内。我认为作为子shell会改变这一点,但只有在一个单独的文件中并在其上运行一个完整的单独shell才能起作用。

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

这是通过设计和POSIX规范实现的。我们可以在
manbash
中阅读:

如果复合命令或外壳函数在忽略
-e
的上下文中执行,则即使设置了
-e
且命令返回故障状态,复合命令或函数体中执行的任何命令都不会受到
-e
设置的影响。如果复合命令或shell函数在忽略
-e
的上下文中执行时设置了
-e
,则在复合命令或包含函数调用的命令完成之前,该设置不会产生任何效果

因此,您应该避免在函数中依赖
set-e

给出以下示例:

函数中的
set-e
被忽略,因为函数是逗号
#!/bin/sh
set -e

my_function() {
    echo "the following command could fail:"
    false || return $?
    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"
the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function
#!/bin/bash

# Get rid of that disgusting set -e.  We don't need it anymore!
# functrace allows RETURN and DEBUG traps to be inherited by each
# subshell and function.  Plus, it doesn't suffer from that horrible
# erasure problem that -e and -E suffer from when the command 
# is used in a conditional expression.
set -o functrace

# A trap to bubble up the error unless our magic command is encountered 
# ('catch=$?' in this case) at which point it stops.  Also don't try to
# bubble the error if were not in a function.
trap '{ 
    code=$?
    if [[ $code != 0 ]] && [[ $BASH_COMMAND != '\''catch=$?'\'' ]]; then
        # If were in a function, return, else exit.
        [[ $FUNCNAME ]] && return $code || exit $code
    fi
}' DEBUG

my_function() {
    my_function2
}

my_function2() {
    echo "the following command could fail:"
    false
    echo "this is after the command that fails"
}

# the || isn't necessary, but the 'catch=$?' is.
my_function || catch=$?
echo "Dealing with the problem with errcode=$catch (⌐■_■)"

echo "run this all the time regardless of the success of my_function"
the following command could fail:
Dealing with the problem with errcode=1 (⌐■_■)
run this all the time regardless of the success of my_function