从子外壳退出外壳脚本


30

请考虑以下代码段:

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if false; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

通常,当func被调用时,它将导致脚本终止,这是预期的行为。但是,如果它是在子外壳中执行的,例如

result=`func`

它不会退出脚本。这意味着调用代码必须每次检查函数的退出状态。有办法避免这种情况吗?这set -e是为了什么?


1
我想要一个函数“ stop”,该函数将一条消息打印到stderr并停止脚本,但是当在子外壳程序中执行调用stop的函数时,它不会停止,例如示例
Ernest AC

2
当然,因为它从子shell退出而不是当前的。只需直接调用函数:func

1
我无法直接调用它,因为它返回的字符串必须存储在变量中
Ernest AC

1
@ErnestAC请在原始问题中提供所有详细信息。上面的函数不返回字符串。

1
@htor我更改了示例
Ernest AC

Answers:


10

可以kill $$在调用之前杀死原始shell()exit,这可能会起作用。但:

  • 对我来说似乎很丑
  • 如果您在其中有第二个子shell,它将损坏,即在子shell中使用一个子shell。

相反,您可以使用以下几种方法之一在Bash FAQ中传回值。不幸的是,其中大多数都不是很好。您可能只需要在每次函数调用后检查错误(-e就有很多问题)。或者,或者切换到Perl。


5
谢谢。不过,我宁愿改用Python。
欧内斯特AC

2
如我所写,这是2019年。告诉某人“切换到Perl”是荒谬的。抱歉,我很抱歉,但是您是否会告诉对C感到沮丧的人切换到IMO等效的Cobol?正如Ernest指出的那样,Python是更好的选择。我的首选是Ruby。无论哪种方式,除了Perl之外都没有。
Graham Nicholls

38

您可以确定退出状态77例如表示退出任何级别的子Shell,然后执行

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

(
  echo here
  (
    echo there
    (
      exit 12 # not 77, exit only this subshell
    )
    echo ici
    exit 77 # exit all subshells
  )
  echo not here
)
echo not here either

set -EERR陷阱结合使用有点像的改进版本set -e,它允许您定义自己的错误处理。

在zsh中,ERR陷阱是自动继承的,因此您不需要set -E,也可以将陷阱定义为TRAPERR()函数,并通过进行修改$functions[TRAPERR],例如functions[TRAPERR]="echo was here; $functions[TRAPERR]"


1
有趣的解决方案!显然比kill $$

3
需要注意的一件事是,该陷阱将无法处理插值命令,例如echo "$(exit 77)";该脚本将像我们写的一样继续进行echo ""
-Warbo

有趣!在(-更老的)bash上是否有没有-E的运气?也许我们必须诉诸于在USER信号上定义陷阱并对该信号使用kill?我也会做一些研究...
Olivier Dulac

如何知道,当不在子shell陷阱中时,为了返回1而不是77?
ceving

7

作为替代方案kill $$,您也可以尝试使用kill 0,它在嵌套子shell的情况下可以工作(所有调用者和辅助进程都将接收到信号)……但是它仍然残酷而丑陋。


2
这个kill进程不是0吗?
欧内斯特AC

5
这将杀死整个过程组。您可能会碰到不需要的东西(例如,如果您在后台启动了一些东西)。
derobert 2012年

2
@ErnestAC参见kill(2)联机帮助页,pids≤0具有特殊含义。
derobert 2012年

0

尝试这个 ...

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if $1; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

echo "shell..."
func $1

echo "subshell..."
result=`func $1`

echo "shell..."
echo "result=$result"

我得到的结果是...

# test_exitsubshell true
shell...
foo
subshell...
shell...
result=foo
# test_exitsubshell false
shell...
something went wrong

笔记

  • 参数化以允许if测试为truefalse(请参阅2次运行)
  • if测试false为时,我们永远不会到达子外壳。

这与用户发布并表示无效的原始想法非常相似。我认为这不适用于subshel​​l案例。使用false的测试会在“ shell”案例之后退出,并且永远不会进入“ subshel​​l”测试案例。我认为在这种情况下它将失败,因为子外壳将从“退出1”调用中退出,但不会将错误传播到外壳。
stickj

0

(特定于Bash的答案)Bash没有例外的概念。但是,在最外层使用set -o errexit(或等效的set -e),失败的命令将导致子外壳退出,并且退出状态为非零。如果这是一组嵌套的子shell,而这些子shell的执行没有条件,则它将有效地“汇总”整个脚本并退出。

当试图将各种bash代码的一部分包含到较大的脚本中时,这可能会很棘手。一小块bash本身可能就可以正常工作,但是在errexit(或不使用errexit)下执行时,行为异常。

[192.168.13.16(f0f5e19e)〜22:58:22]#bash -o errexit / tmp / foo
出了些问题
[192.168.13.16(f0f5e19e)〜22:58:31]#bash / tmp / foo
出了些问题
但是我们还是到了这里
[192.168.13.16(f0f5e19e)〜22:58:37]#cat / tmp / foo
#!/ bin / bash
停 () {
    回显“ $ {1}”
    1号出口
}

如果为假;然后
    回声“ foo”
其他
    (
        停止“出问题了”
    )
    回声“但我们还是到了这里”
科幻
[192.168.13.16(f0f5e19e)〜22:58:40]#

-2

我在一个班轮中退出的示例:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit

1
这似乎并不是一个真正的答案,它可以按照OP的要求在子shell中运行的命令起作用。没有评论或理由的否决票与错误答案一样无济于事。
DVS
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.