如何从一个函数中有效地终止 Bash 脚本的执行

107

我在Bash函数中使用了"exit 1"语句来终止整个脚本,并且它运行良好:

function func()
{
   echo "Goodbye"
   exit 1
}
echo "Function call will abort"
func
echo "This will never be printed"

但后来我意识到,当这样调用时它不会起作用:

res=$(func)

我知道我创建了一个子Shell,“exit 1”会中止子Shell而不是主Shell;有没有办法编写一个函数,无论如何调用它都可以中止整个执行过程?

我只需要获取真实的返回值(由函数输出)。

7个回答

122

可以 这样做,即为顶层 shell 注册 TERM 信号以退出,然后向顶层 shell 发送 TERM

#!/bin/bash
trap "exit 1" TERM
export TOP_PID=$$

function func()
{
   echo "Goodbye"
   kill -s TERM $TOP_PID
}

echo "Function call will abort"
echo $(func)
echo "This will never be printed"

所以,你的函数会向顶级shell发送一个TERM信号,然后使用提供的命令进行捕获和处理,本例中是"exit 1"


1
我曾考虑过C语言中的setsid()函数,但它的工作方式并不完全相同。更新后不使用setsid命令,因为这将要求我们启动一个新进程。 - FatalError
3
能够工作。无论功能嵌套的深度和流程如何……都能够正常工作。 - LiMar
@NeilC.Obremski:当然可以。主要问题在于如何将值传回顶层shell。可能需要使用像tempfile这样的命令来选择一个存储代码的位置,然后让正在退出的shell将代码写入该文件,最后只需读取父处理程序即可。 - FatalError
2
当存在嵌套的子shell时,似乎无法正常工作。在这里查看使用 set -E 的Bash备选解决方案:http://unix.stackexchange.com/questions/48533/exit-shell-script-from-a-subshell - Ron Burk
1
@PeterMortensen TERM 可行... 为什么 SIGTERM 更好呢? - Jonathan Cross
显示剩余3条评论

56

您可以使用set -e,它会在命令以非零状态退出时退出

set -e 
func
set +e

或者获取返回值:

(func) || exit $?

3
有趣的是,我发现了一个解决方案。在主脚本中使用"set -e"会导致任何返回1(或以其退出)的函数中止整个执行过程。不幸的是,"set -e"会彻底改变所有的行为,因此我无法使用它。 - LiMar
1
你应该在数字比较时使用 (( )). 在 [ ] 中应该使用 -gt, -eq 等。 - jordanm
21
res=$(func) || exit翻译为中文:甚至可以将结果赋值给 res,否则就退出。 - glenn jackman
3
这有点违背问题的本意。如果每个函数调用都需要自己捕获错误,那么使用exit就没有任何意义。该函数可以直接返回。然而,如果全局设置了set -e,那么这样做就很有道理。 - phil294
@captainGeech,shellcheck.net会警告您:https://github.com/koalaman/shellcheck/wiki/SC2155 - glenn jackman
显示剩余3条评论

3
最初的回答是,我认为更好的方法是:
#!/bin/bash
set -e
trap "exit 1" ERR

myfunc() {
     set -x # OPTIONAL TO SHOW ERROR
     echo "Exit with failure"
     set +x # OPTIONAL
     exit 1
}
echo "BEFORE..."
myvar="$(myfunc)"
echo "AFTER..But not shown"

1
它有什么优势? - Peter Mortensen
如果myfunc被这样调用:echo "hello $(myfunc)",那么这个方法就不起作用了。 - Jonathan Cross

2

一个子进程不能隐式地强制父进程关闭。您需要使用某种信号机制。选项可能包括特殊的返回值,或者使用 kill 发送某些信号,例如:

function child() {
    local parent_pid="$1"
    local other="$2"
    ...
    if [[ $failed ]]; then
        kill -QUIT "$parent_pid"
    fi
}

1

如果你只需要在函数内部退出脚本,可以使用以下方法:

function die () {
    set -e
    /bin/false
}

在您的函数中,使用"die"代替"exit"。


0

但是有没有一种方法可以编写一个函数,无论如何调用它都会中止整个执行过程呢?

没有。

我只需要获取函数实际返回的值(由函数输出)。

你可以。

res=$(func)
echo $?

0
这个方法有效且没有很多额外的麻烦,确保顶层函数使用圆括号而不是花括号进行定义。这样可以提供以下功能:1)立即退出;2)可用的退出状态。
TestExitAllFunctions ()
(
    function FatalError ()
    {
        echo "$1" 1>&2;
        exit 1
    };
    echo "Function call will abort";
    FatalError "Oops, fatal error found, sorry";
    echo "This will never be printed"
)

TestExitAllFunctions
Function call will abort
Oops, fatal error found, sorry

echo $?
1

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