如何以与pipefail类似的方式让bash在反补失败时退出?


55

因此,我想尽可能地加强bash脚本(并且在无法委托给Python / Ruby这样的语言时),以确保不会出错。

在这种情况下,我有一个strict.sh,其中包含如下内容:

set -e
set -u
set -o pipefail

并以其他脚本来源。但是,尽管pipefail会加速:

false | echo it kept going | true

它不会拾起:

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

输出将是

输出为''

False返回非零状态和no-stdout。在管道中,该方法将失败,但此处未捕获该错误。当实际上是将计算结果存储在变量中以供以后使用,并且该值设置为空时,这可能会导致以后出现问题。

所以-有没有办法让bash将反引号内的非零返回码视为足以退出的原因?

Answers:


51

Single UNIX规范中用于描述的含义的set -e确切语言是:

启用此选项时,如果一个简单命令由于“ Shell错误的后果”中列出的任何原因而失败,或者返回退出状态值> 0,并且不是[条件命令或否定命令],则Shell应该立即退出。

当这样的命令在子shell中发生时会发生什么,这是模棱两可的。从实际的角度来看,所有子外壳程序都可以做的是退出并向父外壳程序返回非零状态。父外壳程序是否将依次退出取决于此非零状态是否转换为在父外壳程序中失败的简单命令。

一个遇到问题的情况就是您遇到的一种情况:命令替换的返回状态为非零。由于此状态被忽略,因此不会导致父外壳退出。正如您已经发现的那样,一种考虑退出状态的方法是在简单的分配中使用命令替换:然后赋值的退出状态就是赋值中最后一个命令替换的退出状态

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

a=$(false)$(echo foo)

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

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

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

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

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

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

如果Bash的任何元素返回非零状态pipefailset -e则Bash的选项将导致管道立即退出。

请注意,进一步复杂的是,set -e除非它处于POSIX模式(set -o posix或者POSIXLY_CORRECT在bash启动时处于环境中),否则bash 在子shell中关闭。

所有这些表明,不幸的是,POSIX规范在指定-e选项方面做得不好。幸运的是,现有的外壳在行为上基本上是一致的。


谢谢你 我的经验是,在较早版本的bash中捕获的一些错误在较早版本中使用set -e会被忽略。我的目的是在任何未处理的错误返回/失败情况导致脚本退出的范围内加强脚本。这是在一个已知的旧系统中,该系统在使用错误的env混乱半小时后会生成垃圾输出文件和“ 0”快乐退出代码-除非您像鹰一样观看输出(并非所有错误)在stderr上,有些在stdout上,有些则是/ dev / null'd),您只是不知道。
丹尼·史泰普

“例如,在以下示例中,所有ash,bash,pdksh,ksh93和zsh都退出而没有显示foo”:BusyBox ash的行为与此不同,它按照规范显示输出。(已通过BusyBox 1.19.4进行了测试。)它也不会以退出set -e; (cd /nonexisting)
dubiousjim

27

(向我提问,因为找到了解决方案。)一种解决方案是始终将其分配给中间变量。这样,$?设置了返回码()。

所以

ABC=`exit 1`
echo $?

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

echo `exit 1`
echo $?

0空行后将输出。echo的返回码(或与反引号输出一起运行的其他命令)将替换0返回码。

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


23

正如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

谢谢之间的矛盾VAR=...local VAR=...真我狼狈不堪!
强尼

13

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

function testcase()
{
    local MYRESULT

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

    return 0
}

输出:

$ testcase
False returned false!
$ 

4

要在命令替换失败时退出,可以-e在子shell中进行显式设置,如下所示:

set -e
x=$(set -e; false; true)
echo "this will never be shown"

1

有趣的一点!

我从来没有偶然发现过,因为我不是set -e(相反,我更喜欢trap ... ERR)的朋友,但是已经测试过:trap ... ERR也不要在其中捕获错误$(...)(或老式的反引号)。

我认为问题是(如此频繁)在这里调用了一个子外壳,并且-e明确地表示当前外壳。

目前想到的其他解决方案是使用read:

 ls -l ghost_under_bed | read name

抛出该异常ERR并带有-e外壳将终止。唯一的问题:这仅适用于具有一行输出的命令(或您通过连接行的内容进行管道传输)。


1
不确定读取技巧,尝试导致未绑定变量。我认为这可能是因为管道的另一侧实际上是一个子外壳,名称将不可用。
丹尼·
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.