【问题标题】:ANDing exit codes in bash与 bash 中的退出代码
【发布时间】:2013-04-27 18:44:50
【问题描述】:

我有一个 bash 脚本,它对我的​​源代码运行三个检查,如果所有命令都成功,则 exit 0,如果其中任何一个失败,则 exit 1

#!/bin/bash

test1 ./src/ --test-1=option
exit_1=$?

test2 ./src/ test-2-options
exit_2=$?

test3 ./src/ -t 3 -o options
exit_3=$?

# Exit with error if any of the above failed
[[ $exit_1 -eq 0 && $exit_2 -eq 0 && $exit_3 -eq 0 ]]
exit $?

此代码有效,但感觉过于冗长和冗长。有什么办法可以让这变得更好吗?具体来说,我不满意:

  • 必须运行命令,然后将退出代码分配给变量
  • 必须使用[[ ... ]]然后在下一行收集其退出代码以退出
  • 必须明确地将变量与 0 进行比较,如 [[ $var -eq 0 ]],而不是将它们视为布尔值

理想情况下,最终结果应该是更具可读性的内容,例如:

exit_1=( test1 ./src/ --test-1=option )
exit_2=( test2 ./src/ test-2-options )
exit_3=( test3 ./src/ -t 3 -o options )

# Exit with error if any of the above failed
exit ( $exit_1 && $exit_2 && $exit_3 )

我考虑过的一些事情:


在一行中将错误代码放入变量中:

exit_1=$( test1 ./src/ --test-1=option )$?
exit_2=$( test2 ./src/ test-2-options )$?
exit_3=$( test3 ./src/ -t 3 -o options )$?

这行得通,并且缩短了一点,但我以前从未见过其他人使用它。这是明智/理智的事情吗?这有什么问题吗?


只是运行测试,然后 && 一起:

test1 ./src/ --test-1=option && \
test2 ./src/ test-2-options && \
test3 ./src/ -t 3 -o options
status=$?

起作用,因为 bash 短路。如果test1 失败,test2 和 test3 不会运行,我希望它们都运行。


使用|| exit检测错误并退出

[[ $exit_1 -eq 0 && $exit_2 -eq 0 && $exit_3 -eq 0 ]] || exit 1

这节省了一行尴尬的退出代码和变量,但exit 1 的重要部分现在位于行尾,您可能会错过它。理想情况下,这样的事情会起作用:

exit [[ $exit_1 -eq 0 && $exit_2 -eq 0 && $exit_3 -eq 0 ]]

当然,这起作用,因为[[ 返回它的输出而不是回显它。

exit $( [[ $exit_1 -eq 0 && $exit_2 -eq 0 && $exit_3 -eq 0 ]] ; echo $? )

确实有效,但仍然看起来像一个可怕的结块


没有明确处理作为布尔值的退出代码

[[ $exit_1 && $exit_2 && $exit_3 ]]

这并没有做你希望它做的事情。 && 将三个返回码存储在变量中的最简单方法是使用完整的$var -eq 0 && ...。肯定有更好的方法吗?


我知道 bash 不是一种很好的编程语言 - 如果你甚至可以这样称呼它 - 但有什么办法可以让这不那么尴尬吗?

【问题讨论】:

  • var=$(foo)$? 方法很有趣,但会将您的测试调用置于不必要的子外壳中。
  • 至少,如果[[ ... && ... && ... ]] 是脚本的最后一条语句,那么最后的exit $? 是不必要的。
  • 有谁知道如何将此应用于 docker-compose --exit-code-from 标志?它允许 docker compose 假设服务的退出代码。如果任何服务的退出代码为 1,我希望 docker compose 的退出代码为 1,否则为 0。
  • stackoverflow.com/questions/29568352/… 看起来这个答案可以工作。您只需在运行 compose 后列出所有容器并聚合它们的退出代码。当您看到第一个退出代码 1 时不会中止,这是您在使用 --exit-code-from 标志时看到的行为

标签: bash


【解决方案1】:

您可以使用bash 的算术命令将OR 退出代码一起使用,并对结果取反,如果任何代码非零,则退出代码为1。先举个例子:

$ ! (( 0 | 0 | 0 )); echo $?
0
$ ! (( 1 | 0 | 0 )); echo $?
1

现在,你的脚本:

#!/bin/bash

test1 ./src/ --test-1=option; exit_1=$?
test2 ./src/ test-2-options;  exit_2=$?   
test3 ./src/ -t 3 -o options; exit_3=$?

# Exit with error if any of the above failed. No need for a final
# call to exit, if this is the last command in the script
! (( $exit_1 || $exit_2 || $exit_3 ))

或者一般来说,您可以在运行任意数量的测试时累积退出代码:

#!/bin/bash

# Unfortunately, ||= is not an assignment operator in bash.
# You could use |=, I suppose; you may not be able to assign
# any meaning to any particular non-zero value, though.
test1 ./src/ --test-1=option; (( exit_status = exit_status || $? ))
test2 ./src/ test-2-options;  (( exit_status = exit_status || $? ))  
test3 ./src/ -t 3 -o options; (( exit_status = exit_status || $? ))
# ...
testn ./src "${final_option_list[@]}"; (( exit_status = exit_status || $? ))

exit $exit_status   # 0 if they all succeeded, 1 if any failed

【讨论】:

  • ! (( $exit_1 || $exit_2 || $exit_3 )) 非常有用。我试图避免将exit_1=$? 赋值放在行尾,因为一些重要的语句会隐藏在长行的末尾。为此,我使用了$exit_1=$( ... )$? 解决方案,但也使用了您的解决方案。谢谢!
  • 实际上,放弃它。我不能使用$exit_1=$( ... )$?,因为它收集了命令的输出和返回码。我想我会使用你的全部答案。
  • 你不用! $exit_status; exit $?吗?
  • 否 @matthew-d-scholefield,exit 将所需的退出代码作为参数。 Source.
  • ! (( $exit_1 || $exit_2 || $exit_3 )):那不应该没有!吗?如果任何 vars > 0,我想要一个 1 作为退出代码 ...否则一个 0
【解决方案2】:

我自己也在寻找这个问题的答案,并决定采用类似于 @chepner 的方式,但不使用 bash 的算术表达式:

#!/bin/bash

failed=0
test1 ./src/ --test-1=option || failed=1
test2 ./src/ test-2-options || failed=1
test3 ./src/ -t 3 -o options || failed=1

# Exit with error if any of the above failed
if [ "$failed" -ne 0 ] ; then
    exit
fi

您只需在开头设置一个标志,如果任何语句失败,则将该标志设置为 1。 || 是这里的关键。

【讨论】:

    【解决方案3】:

    一些改进

    [ $exit_1$exit_2$exit3 = 000 ]
    # no exit needed here, script exits with code from last command
    

    【讨论】:

      【解决方案4】:

      为什么需要三个不同的变量?

      fn() {
          local -i st
          test1 ./src/ --test-1=option
          st=$?
      
          test2 ./src/ test-2-options
          (( st = ($? || st) )) # Use arithmetic expression to or the values at each step
      
          test3 ./src/ -t 3 -o options
          (( 0 == ($? || st) ))
      }
      

      【讨论】:

        【解决方案5】:

        您可以使用此行指定命令的退出代码:

        RES1=$(CMD > /dev/null)$?
        

        示例:

        RES1=$(test1 ./src/ --test-1=option > /dev/null )$?
        

        所以你的代码将是:

        exit_1=$( test1 ./src/ --test-1=option > /dev/null )$?
        exit_2=$( test2 ./src/ test-2-options > /dev/null )$?
        exit_3=$( test3 ./src/ -t 3 -o options > /dev/null )$?
        
        # Exit with error if any of the above failed
        exit ( $exit_1 && $exit_2 && $exit_3 )
        

        【讨论】:

        • 一个我不知道的巧妙技巧,但这会创建一个不必要的子shell,所以我不能凭着良心对其进行投票。
        • 当测试脚本启动一个测试网络服务器和wget --mirror 以测试断开的链接、比较多个文件夹、相互比较,并在一些文件中进行grep 查找剩余的TODOs文件,启动子shell是我可以忍受的!不过,如果速度和效率一直是个问题,这仍然是一件好事。
        • 其实这根本行不通!如果子shell 中的命令产生任何输出(我的命令会这样做),那么输出将分配给变量。这意味着out=$( echo 'foo' ; exit 1 )$? 导致$out 成为'foo1'
        • 对不起,我的错误。自从我使用它以来已经有一段时间了。无论如何,我修复了答案,现在它可以工作了。我还删除了“if”注释,因为我不确定那个注释。
        • 不幸的是,我需要打印命令的输出,因此重定向到 /dev/null 不是一种选择。我将在命令运行后收集退出状态
        猜你喜欢
        • 2016-08-16
        • 2023-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-11
        • 2018-09-05
        • 1970-01-01
        相关资源
        最近更新 更多