在bash中使用set -e / set +e与函数相关

16

我一直在我的脚本中使用类似这样的简单bash开头:

#!/bin/bash
set -e

与模块化/使用函数一起,今天它让我遇到了麻烦。

假设我在某个地方有一个函数,像这样

foo() {
  #edit: some error happens that make me want to exit the function and signal that to the caller 
  return 2
}

理想情况下,我希望能够使用多个小文件,在其他文件中包含它们的函数,然后像这样调用这些函数:

set +e
foo
rc=$?
set -e

这对于仅有两层子例程的情况有效。但是如果foo也调用那样的子程序,返回之前的最后一个设置将为set -e,这将使脚本在返回时退出 - 我无法在调用函数中覆盖此设置。因此,我必须做的是

foo() {
  #calling bar() in a shielded way like above
  #..      

  set +e
  return 2
}

我觉得这非常反直觉(也不是我想要的-如果在某些情况下我想使用该函数而无需对抗故障,而在其他情况下我想处理清理怎么办?)最好的处理方式是什么?顺便说一句,我正在OSX上进行此操作,我还没有测试过在Linux上是否有不同的行为。


等一下,你能详细说明一下吗?我需要在调用者中添加吗?冒号有什么作用? - Michel Müller
你能详细说明一下我是如何滥用return的吗?那么我应该如何在函数内部发出错误信号呢?(如果我表达不清楚,很抱歉 - return 2 只在错误条件下执行。) - Michel Müller
1
你不是,但我以为你是。我的错,抱歉。 - Martin Tournoij
1个回答

21

Shell 函数实际上没有“返回值”,只有退出码。

你可以在调用者中添加 && :,这使得命令“经过测试”,并且不会退出它:

foo() {
    echo 'x'
    return 42
}

out=$(foo && :)
echo $out
:是“null命令”(即它不执行任何操作)。在这种情况下,它甚至不会被执行,因为它只有在foo返回0(但实际上没有返回)时才会运行。
这将输出:
x

这可能有些丑陋,但话说回来,所有的Shell脚本都可以说是有点丑陋;-)

引用FreeBSD中的sh(1),它比bash的手册解释得更好:

 -e errexit
         Exit immediately if any untested command fails in non-interactive
         mode.  The exit status of a command is considered to be explicitly
         tested if the command is part of the list used to control an if,
         elif, while, or until; if the command is the left hand operand of
         an “&&” or “||” operator; or if the command is a pipeline preceded
         by the ! operator.  If a shell function is executed and its exit
         status is explicitly tested, all commands of the function are con‐
         sidered to be tested as well.

谢谢,我明白了。所以从现在开始,如果我想要以“测试”的方式执行一个函数,我会这样做: set +e foo && : rc=$? set -e - Michel Müller
1
@MichelMüller,你不需要对set做任何操作;你只需要在脚本中使用一次set -e来启用它,然后为了“规避”它,使用&& :即可。 - Martin Tournoij
这是一个相当晚的跟进,还请多包涵。我刚在想,当我进行foo && : \n rc=$?以便处理调用者的错误情况时,写foo && rc=$?是否更清晰易懂?这种写法是否符合最佳实践? - Michel Müller
@MichelMüller 我认为大多数人会觉得每行一个语句通常是最好的做法;但是不同的意见存在。这取决于你为谁编写代码(你自己?客户?组织内部使用?),预期的代码标准等等... - Martin Tournoij
这个语法应该是 out=$(foo || :),即如果 foo 的退出状态是非零(假),那么该状态会与空语句的 ':' 退出状态进行逻辑或运算,而 ':' 的退出状态为真,因此 假 || 真 == 真,从而执行下面的语句 echo $out - Jim Fischer

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