在不退出 shell 的情况下使用带有 set -e 的 bash 函数

8
考虑以下函数:
function current_dir {
  set -e
  git foobar
  echo "I will not print this, because git foobar returned a non-zero exit code"
}

现在,当我调用这个函数并在我的shell中尝试调用它时,它不仅会退出函数,还会退出shell本身。如何避免这种情况发生?

不要使用 set -e?它是为脚本而设计的,不适合交互式使用。 - chepner
我明白了,但问题是如何在半交互式脚本(函数)中获得相同的功能。 - Niels B.
就像我说的那样,从函数中删除 set -e。将其添加到您希望在任何错误时退出的脚本的顶部,但不要将其添加到可能在交互式和非交互式 shell 中使用的特定函数中。 - chepner
好的,此函数是直接从 shell 中调用和执行的,所以这不是一个选项。我正在寻找一种方法,编写一个 bash 函数,如果函数内部的任何命令执行失败,则立即返回。 - Niels B.
3个回答

5

如果您不需要在当前shell中执行函数(例如,它没有设置任何需要对调用者可见的参数值),则可以将函数体制作为子shell,而不是命令组:

current_dir () (
    set -e
    git foobar
    echo "I will not print this, because git foobar returned a non-zero exit code"
)

在GNU bash,版本4.3.11(1)-release (x86_64-pc-linux-gnu)中似乎无法工作:附近有意外的标记“newline”的语法错误。 - Marcin Owsiany
哎呀,这似乎在任何版本的bash中都不起作用;问题在于function关键字,因此更有理由避免使用它并使用POSIX风格的函数定义:current_dir () ( ... ) - chepner

4
据我所知,set -e会完全按照您看到的效果执行:一旦命令以非零状态退出,就会完全退出shell。
您可以尝试使用trap重新编写函数或在命令之间使用&&
function current_dir {
  git foobar && echo "I will not print this, because git foobar returned a non-zero exit code"
}

或者(更易读):
function current_dir {
  trap 'trap - ERR; return' ERR
  git foobar
  ...other commands...
  echo "I will not print this, because a command returned a non-zero exit code"
}

如果出于某些原因确实需要set -e,您可以使用set +e暂时禁用它,并在关键部分之后重新启用它。


1
我理解这里的想法,但是一旦代码行堆积起来,可读性就会受到影响。 - Niels B.
1
你可以在 && 后立即开始新的一行,或者使用 if 语句代替 && 列表。 - chepner
在这种情况下,像我添加的示例一样使用 trap ... ERR - Eric Fournie
1
我认为 trap…ERR 是正确的,但没有必要完全终止当前 shell。为什么不使用 current_dir() { trap 'trap - ERR; return' ERR; git foobar; …; } - kojiro
我试过了,没有注意到自己打错了一个字母,以为在trap中不可能使用return,在bash中有太多的错误方式了 :). 当然这样做更好!我会纠正我的回答。但是为什么要在trap内部再次使用trap呢? - Eric Fournie
显示剩余2条评论

3
"

set -e" 用于在命令(管道或子shell命令等)以非零状态退出时立即退出。

默认情况下,BASH会忽略错误并继续解释您的脚本。这可能会导致非常糟糕的意外结果,例如:

    if ! lss myLock
    then touch myLock; echo "No lock, I can do the job"; rm -f myLock
    fi

这个脚本的结果是:
bash: lss : commande introuvable
No lock, I can do the job

正如您所看到的,BASH在未找到命令和命令执行失败之间没有区别。
“-e”经常用于确保Shell解释器在错误后立即停止(因此我们必须考虑所有错误...)。这有助于防止在脚本中犯错误时出现非常严重的问题(想象一下当您忘记设置v时执行rm -rf“$v” / *的情况;-))。为此,我们在shebang中传递“-e”选项。显然,它不是为交互式使用而设计的,我也无法想象“set -e”或“set +e”的良好用法(但可以测试)。
回答您最初的问题。您可以通过应用以下解决方案之一来避免终止shell:

使用if语句

function myF1() {
    if git foobar; then
        echo "I will not print this, because git foobar returned a non-zero exit code"
    fi
}

使用控制运算符 &&

function myF2() {
    git foobar && \
    echo "I will not print this, because git foobar returned a non-zero exit code"
}

打开一个子shell
function myF4() {(
    set -e
    git foobar
    echo "I will not print this, because git foobar returned a non-zero exit code"
)}

在这种情况下,“set -e”将立即退出子shell,但不会终止调用者:-)
在这里使用陷阱不是一个好主意,因为它会对调用者产生副作用(陷阱被全局定义)。显然,我们可以结合陷阱和子shell,但在您的情况下没有用处。

1
我喜欢这里的子shell想法。然而,副作用是您将无法访问调用shell的状态 - 例如自定义变量等。 - Tim Malone

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