即使设置了 -e,函数内的命令替换也不会在失败时停止脚本

Hir*_*i_U 7 bash shell-script

我有一个以下脚本sandbox.sh

#!/bin/bash
set -eu -o pipefail -E

function func1() {
  echo "FUNC1"
  exit 1
}

function func2() {
  local ret
  ret=$(func1)
  echo $ret
  echo "(func2)This line shouldn't be reached:'${?}'" >&2
}

var=$(func1) # The Line
echo "main:This line shouldn't be reached:'${var}':'${?}'" >&2
Run Code Online (Sandbox Code Playgroud)

(GNU bash,版本 4.4.20(1)-release (x86_64-pc-linux-gnu))

这会按预期停止执行,

$ bash -eu sandbox.sh 
$ 
Run Code Online (Sandbox Code Playgroud)

但是,如果我将“The Line”修改为var=$(func2)call func1through func2,它将给出以下输出

$ bash sandbox.sh 
(func2)This line shouldn't be reached:'0'
main:This line shouldn't be reached:'FUNC1':'0'
$ 
Run Code Online (Sandbox Code Playgroud)

对我来说,当命令替换放置在函数中时,它的行为似乎有所不同,但我不明白为什么 bash 是这样设计的。另外,很可能出现一个函数的输出被另一个函数使用的情况,这种差异令人困惑。

注意:如果我像下面这样重写 func2 ,

function func2() {
  func1
}
Run Code Online (Sandbox Code Playgroud)

剧本停在The Line。然而,我相信,程序员经常想要操作 func1 的输出。

bob*_*ogo 5

如果我们慢慢地逐步执行,这一切都是完全可以理解的。\n需要更多日志记录,\n因此使用参数运行bash-x,\n这将在 bash 执行命令之前回显命令,\n前缀为+ .

\n

第一次运行

\n
$ bash -x sandbox.sh; echo $?\n+ set -eu -o pipefail -E\n++ func1\n++ echo FUNC1\n++ exit 1\n+ var=FUNC1\n1\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • -e表示shell 将立即退出,命令返回非零。\n但重要的是,您func1在子 shell 中运行(使用$( ))。\n上面的跟踪通过使用两个+s 作为前缀 ( ++ ) 显示了这一事实。
  • \n
  • 子 shell 在 stdout 上输出FUNC1,然后退出并返回代码 1。\n
      \n
    • 注意:-e在该子 shell 内关闭。子 shell 退出的原因是由于exit命令而不是-e. func1由于所写的方式,您无法真正说出这一点。
    • \n
    \n
  • \n
  • 回到第一个 shell,我们分配FUNC1给变量var。然而,这个赋值命令的退出代码是最后一个命令替换的退出代码Bash看到此失败(即非零退出代码)并退出。
  • \n
\n

引用手册的简单命令扩展部分:

\n
\n

如果其中一个扩展包含命令替换,则该命令的退出状态是最后执行的命令替换的退出状态。

\n
\n

第二次运行

\n

与第一次运行的解释完全相同。\n我们再次注意到,它-e在子 shell 内不起作用。\n但是,这一次,\n存在重大差异 \xe2\x80\x94 我们可以更清楚地了解正在发生的情况。

\n
    \n
  • 的退出代码func2是其最后一个命令的退出代码
  • \n
  • echo总是成功的。
  • \n
  • func2总是成功
  • \n
  • 任务总是成功的。
  • \n
\n

-e没有影响。

\n

shopt -s inherit_errexit

\n

这将在子 shell 中打开-e。\n然而,这是一个困难的伙伴。\n它不能保证我们在命令失败时断言。

\n

考虑一下:

\n
set -e\nshopt -s inherit_errexit\n\nf() { echo a; (exit 22); echo b; }\n\necho "f says [$(f)] $?"\necho byee\n
Run Code Online (Sandbox Code Playgroud)\n

这次命令替换是 的一部分echo,而不是赋值的一部分,我们得到

\n
+ set -e\n+ shopt -s inherit_errexit\n++ f\n++ echo a\n++ exit 22\n+ echo 'f says [a] 22'\nf says [a] 22\n+ echo byee\nbyee\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 子 shell 看到一个失败的命令,并显示退出代码 22。由于该命令-e有效,因此 shell 会以代码 22 退出(echo b不执行)。
  • \n
  • 返回第一个 shell,echo获取a的输出f以及22子 shell 的退出代码
  • \n
  • 事实是,与赋值不同,退出代码为零echo
  • \n
\n

版本

\n
$ bash --version\nGNU bash, version 5.0.17(1)-release (x86_64-redhat-linux-gnu)\nCopyright (C) 2019 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\nThis is free software; you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\n
Run Code Online (Sandbox Code Playgroud)\n