bash中的退出码

bash中的退出码,bash,Bash,我有一个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 [[ $

我有一个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=$?
这很管用,并且使它变得更短,但我以前从未见过其他人使用它。这样做明智/明智吗?这有什么问题吗


只需运行测试,并将它们放在一起:

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
这就节省了一行笨拙的退出代码和变量,但退出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不是一种很好的编程语言——如果你可以这样称呼它的话——但是有什么方法可以让它不那么尴尬吗?

一些改进

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

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

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 )

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

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) ))
}

您可以使用
bash
的算术命令将退出代码一起
,并对结果求反,如果其中任何代码为非零,则得到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

我自己也在寻找答案,并决定采用类似于@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。
|
是这里的关键。

我不知道这是一个巧妙的技巧,但这会创建一个不必要的子shell,因此我不能出于良心将其设置为1。因为测试脚本会启动一个测试web服务器并
wget--mirror
,以测试是否存在损坏的links,diff多个文件夹,彼此相对,grep一些文件以保留文档中的
TODO
s,启动子shell是我可以接受的!但是,如果速度和效率是个问题,这仍然很好。实际上,这根本不起作用!如果子shell中的命令产生任何输出(我的命令就是这样做的),然后将输出分配给变量。这意味着
out=$(echo'foo';exit 1)$?
导致
$out
成为
'foo1'
。对不起,我的错误。我已经有一段时间没有使用它了。不管怎样,我修复了答案,现在它可以工作了。我还删除了“if”注释,因为我不确定该注释是否正确。不幸的是,我需要打印命令的输出,因此重定向到/dev/null不是一个选项。我只需收集t命令运行后的退出状态
var=$(foo)$?
方法很有趣,但会将测试调用放入不必要的子shell中。至少,如果
[[…&&&…&&&&&…&&]],则不需要最后的
exit$?
是脚本的最后一条语句。有人知道如何将此应用于docker compose--exit code from flag吗?它允许docker compose假定服务的退出代码。如果任何服务的退出代码为1,则docker compose的退出代码为1,否则为0。看起来这个答案可行。您只需列出一个运行后,编写并聚合他们的退出代码。当您看到第一个退出代码1时,您不会中止,这是您在使用--exit code from flag
!($exit_1 | |$exit_2 | |$exit_3))时看到的行为
非常有用。我试图避免将
exit_1=$?
赋值放在行的末尾,因为一些重要语句隐藏在长行的末尾。为此,我使用了
$exit_1=$(…)$?
解决方案,但在其他方面使用了您的解决方案。谢谢!实际上,放弃它。我不能使用
$exit_1=$(…)$?
,因为它同时收集命令的输出和返回代码。我想我将使用您的全部答案。您不必执行
!$exit\u status;exit$?
?否@matthew-d-scholefield,exit将所需的退出代码作为参数。