21

我有一个 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 不是一种很好的编程语言——如果你甚至可以这样称呼它的话——但是有什么办法可以让这变得不那么尴尬吗?

4

5 回答 5

16

您可以对退出代码一起使用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
于 2013-05-03T12:30:41.863 回答
9

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

于 2015-04-24T15:29:18.060 回答
4

一些改进

[ $exit_1$exit_2$exit3 = 000 ]
# no exit needed here, script exits with code from last command
于 2013-05-03T11:20:27.320 回答
0

您可以使用此行分配命令的退出代码:

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 )
于 2013-05-03T11:25:59.987 回答
0

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

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) ))
}
于 2013-05-03T12:08:16.457 回答