如何以与 Pipefail 类似的方式让 bash 在反引号失败时退出?

如何以与 Pipefail 类似的方式让 bash 在反引号失败时退出?

因此,我喜欢尽可能强化我的 bash 脚本(当无法委托给 Python/Ruby 这样的语言时),以确保错误不会被捕获。

本着这种精神,我有一个 strict.sh,其中包含以下内容:

set -e
set -u
set -o pipefail

并将其来源到其他脚本中。然而,虽然管道故障会出现:

false | echo it kept going | true

它不会拾取:

echo The output is '`false; echo something else`' 

输出将是

输出是''

False 返回非零状态,并且无标准输出。在管道中它会失败,但这里错误未被捕获。当这实际上是存储在变量中供以后使用的计算并且该值设置为空白时,这可能会导致以后出现问题。

那么 - 有没有办法让 bash 将反引号内的非零返回码视为足以退出的理由?

答案1

中使用的确切语言单一 UNIX 规范来描述的含义set -e是:

当此选项打开时,如果简单的命令由于列出的任何原因而失败Shell 错误的后果或返回退出状态值 >0,并且不是 [条件或否定命令],则 shell 应立即退出。

当这样的命令发生在一个子外壳。从实际角度来看,子 shell 所能做的就是退出并向父 shell 返回非零状态。父 shell 是否会依次退出取决于此非零状态是否会转换为在父 shell 中失败的简单命令。

您遇到的就是这样一个有问题的情况:来自 a 的非零返回状态命令替换。由于该状态被忽略,因此不会导致父 shell 退出。作为你已经发现了,考虑退出状态的一种方法是使用简单的命令替换任务: 然后分配的退出状态是分配中最后一个命令替换的退出状态

请注意,仅当存在单个命令替换时,这才会按预期执行,因为仅考虑最后一个替换的状态。例如,以下命令是成功的(根据标准以及我见过的每个实现):

a=$(false)$(echo foo)

另一种需要注意的情况是显式子shell: (somecommand)。根据上面的解释,子 shell 可能会返回非零状态,但由于这不是父 shell 中的简单命令,因此父 shell 应该继续。事实上,我所知道的所有 shell 都会在此时返回父级。虽然这在许多情况下很有用,例如使用括号将当前目录更改等操作保留在本地,但如果在子 shell 中关闭,或者子 shell 以以下方式返回非零状态,则(cd /some/dir && somecommand)它违反了规范:set -e不会终止它,例如!在 true 命令上使用。例如,This should be displayed以下示例中 ash、bash、pdksh、ksh93 和 zsh 均退出而不显示:

set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"

set -e然而,在生效期间,没有一个简单的命令会失败!

第三个有问题的情况是一个不平凡的元素管道。实际上,所有 shell 都会忽略除最后一个之外的管道元素的故障,并表现出与最后一个管道元素有关的两种行为之一:

  • ATT ksh 和 zsh 在父 shell 中执行管道的最后一个元素,照常工作:如果一个简单的命令在管道的最后一个元素中失败,则执行该命令的 shell(恰好是父 shell),退出。
  • 如果管道的最后一个元素返回非零状态,其他 shell 会通过退出来近似行为。

与之前一样,在管道的最后一个元素中关闭set -e或使用否定会导致它以不应该终止 shell 的方式返回非零状态;然后,除 ATT ksh 和 zsh 之外的 shell 将退出。

巴什的pipefailset -e如果管道的任何元素返回非零状态,选项会导致管道立即退出。

set -e请注意,更复杂的是,bash在命令替换中关闭,但不是常规子 shell(即在``or内部$(),但不在()),除非 bash 处于 POSIX 模式(即set -o posixPOSIXLY_CORRECT在 bash 启动时在环境中设置,或者 bash 被调用为sh),在这种情况下, 的当前设置e是从父 shell 继承的调用时间(无论是命令替换还是子 shell)。

不幸的是,所有这些都表明 POSIX 规范在指定选项方面做得很差-e。幸运的是,现有的 shell 的行为大多是一致的。

答案2

(回答我自己的问题,因为我找到了解决方案)一种解决方案是始终将其分配给中间变量。这样就设置了 returncode ( $?)。

所以

ABC=`exit 1`
echo $?

将输出(或者如果存在1则退出),但是:set -e

echo `exit 1`
echo $?

0将在空行后输出。 echo(或使用反引号输出运行的其他命令)的返回代码将替换 0 返回代码。

我仍然对不需要中间变量的解决方案持开放态度,但这给了我一些帮助。

答案3

正如OP在他自己的回答中指出的那样,将子命令的输出分配给变量确实可以解决问题;毫发无伤$?

然而,一种边缘情况仍然会让您困惑于漏报(即命令失败但错误不会出现),local变量声明:

local myvar=$(subcommand)将要总是返回0

bash(1)指出这一点:

   local [option] [name[=value] ...]
          ... The return status is 0 unless local is used outside a function,
          an invalid name is supplied, or name is a readonly variable.

这是一个简单的测试用例:

#!/bin/bash

function test1() {
  data1=$(false) # undeclared variable
  echo 'data1=$(false):' "$?"
  local data2=$(false) # declaring and assigning in one go
  echo 'local data2=$(false):' "$?"
  local data3
  data3=$(false) # assigning a declared variable
  echo 'local data3; data3=$(false):' "$?"
}

test1

输出:

data1=$(false): 1
local data2=$(false): 0
local data3; data3=$(false): 1

答案4

正如其他人所说,local总是返回 0。解决方案是先声明变量:

function testcase()
{
    local MYRESULT

    MYRESULT=$(false)
    if (( $? != 0 )); then
        echo "False returned false!"
        return 1
    fi

    return 0
}

输出:

$ testcase
False returned false!
$ 

相关内容