使用“bash -e”时如何获取子shell的输出和退出值?

使用“bash -e”时如何获取子shell的输出和退出值?

考虑以下代码

外部范围.sh

#!/bin/bash
set -e
source inner-scope.sh
echo $(inner)
echo "I thought I would've died :("

内部范围.sh

#!/bin/bash
function inner() { echo "winner"; return 1; }

我试图outer-scope.sh在调用inner()失败时退出。由于$()调用了子 shell,所以不会发生这种情况。

我还怎样才能获得函数的输出,同时保留函数可能以非零退出代码退出的事实?

答案1

$()保留退出状态;您只需在没有自己状态的语句中使用它,例如赋值。

输出=$(内部)

在此之后,$?将包含的退出状态inner,并且您可以使用各种检查它:

output=$(inner) || exit $?
echo $output

或者:

if ! output=$(inner); then
    exit $?
fi
echo $output

或者:

if output=$(inner); then
    echo $output
else
    exit $?
fi

(注意:exit没有参数的裸命令相当于exit $?- 也就是说,它以最后一个命令的退出状态退出。我使用第二种形式只是为了清楚起见。)


另外,需要注意的是:source在这种情况下完全不相关。您只需inner()outer-scope.sh文件中定义,即可获得相同的结果。

答案2

根据内置命令 set 的手册,shell 确实不是如果出现以下情况则退出:

  1. 失败的命令是紧跟whileuntil关键字的命令列表的一部分,
  2. 测试的一部分在if声明中,
  3. &&或列表中执行的任何命令的一部分,除了最后一个或||之后的命令,&&||
  4. 管道中的任何命令,但最后一个命令除外,
  5. 或者如果命令的返回状态被反转!

以上任何一种方法都可以,除了第一种方法,因为你没有运行循环。不过为了便于阅读,最好使用以下if语句:

set -e

if x=$(echo a; false); then
  echo "subshell worked"
else
  echo "subshell failed"
fi

echo "x: $x"

或者你可以反转结果:

bash -c 'set -e; ! x=$(echo a; false); echo "[$?]x:$x"'   # [0]x:a
bash -c 'set -e; ! y=$(echo b; true ); echo "[$?]y:$y"'   # [1]y:b

但要注意结果现在被反转了。幸运的是,即使成功的子 shell 被反转,bash 也不会退出。

有趣的是,下面的方法似乎也有效,通过将子 shell 调用分组到列表中,然后反转整个列表,而不仅仅是子 shell 调用:

bash -c 'set -e; ! { x=$(echo a; false); echo "[$?]x:$x"; }'  # [1]x:a

这在某种程度上与上面的第 3 种情况相匹配,它也是一个列表,只是它没有使用&&||


内联初始化的特殊情况

注意函数局部变量的一个棘手情况,比较下面两个几乎相同的函数:

f() { local    v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }

在这两个函数中,子 shell 都以false导致其失败的命令结束,然而,在执行时,我们会得到:

$ f     # fooled by 'local' with inline initialization
output:data, status:0

$ g     # good one, with separated declaration and initialization
output:data, status:1

为什么?

在 Bash 中,local实际上是一个内置命令当子 shell 的输出用于初始化local变量时,退出状态不再是子 shell 的退出状态,而是local 命令,只要0局部变量被声明即可。

也可以看看https://stackoverflow.com/a/4421282/537554

答案3

#!/bin/bash
set -e
source inner-scope.sh
foo=$(inner)
echo $foo
echo "I thought I would've died :("

通过添加echo,子 shell 不会独立存在(不会单独检查)并且不会中止。赋值可以避免这个问题。

您也可以这样做,并将输出重定向到文件,以便稍后处理。

tmpfile=$( mktemp )
inner > $tmpfile
cat $tmpfile
rm $tmpfile

相关内容