是否有一种优雅的方式来存储和评估bash脚本中的返回值?

时间:2014-01-23 19:50:52

标签: bash shell return-value sh

我在bash中有一系列相当复杂的命令,最终会返回一个有意义的退出代码。脚本后面的各个地方需要有条件地分支命令集是否成功。

目前我正在存储退出代码并以数字方式对其进行测试,如下所示:

long_running_command | grep -q trigger_word
status=$?

if [ $status -eq 0 ]; then
    : stuff
else

: more code

if [ $status -eq 0 ]; then
    : stuff
else

出于某种原因,感觉这应该更简单。我们存储了一个简单的退出代码,现在我们重复输入数值测试操作来运行它。例如,我可以欺骗使用字符串输出而不是返回代码,这更容易测试:

status=$(long_running_command | grep trigger_word)

if [ $status ]; then
    : stuff
else

: more code

if [ $status ]; then
    : stuff
else

表面上看起来更直接,但我发现它很脏。

如果其他逻辑不是那么复杂而且我只运行了一次,我意识到我可以将它嵌入到测试运算符的位置,但是当你需要在其他位置重用结果时,这并不理想无需重新运行测试

if long_running_command | grep -q trigger_word; then
    : stuff
else

到目前为止,我唯一发现的是将代码作为命令替换的一部分进行分配:

status=$(long_running_command | grep -q trigger_word; echo $?)

if [ $status -eq 0 ]; then
    : stuff
else

即使这在技术上也不是一次性任务(虽然有些人可能认为可读性更好),但必要的数值测试语法对我来说仍然很麻烦。也许我只是在成为强迫症。

我是否错过了一种更优雅的方式将退出代码分配给变量,然后再对其进行分支?

6 个答案:

答案 0 :(得分:3)

简单的解决方案:

output=$(complex_command)
status=$?

if (( status == 0 )); then
    : stuff with "$output"
fi

: more code

if (( status == 0 )); then
    : stuff with "$output"
fi

或更多eleganter-ish

do_complex_command () { 
    # side effects: global variables
    # store the output in $g_output and the status in $g_status
    g_output=$(
        command -args | commands | grep -q trigger_word
    )
    g_status=$?
}
complex_command_succeeded () {
    test $g_status -eq 0
}
complex_command_output () {
    echo "$g_output"
}

do_complex_command

if complex_command_succeeded; then
    : stuff with "$(complex_command_output)"
fi

: more code

if complex_command_succeeded; then
    : stuff with "$(complex_command_output)"
fi

do_complex_command () { 
    # side effects: global variables
    # store the output in $g_output and the status in $g_status
    g_output=$(
        command -args | commands
    )
    g_status=$?
}
complex_command_output () {
    echo "$g_output"
}
complex_command_contains_keyword () {
    complex_command_output | grep -q "$1"
}

if complex_command_contains_keyword "trigger_word"; then
    : stuff with "$(complex_command_output)"
fi

答案 1 :(得分:3)

为什么不为稍后需要发生的事情设置标记?

cheeseballs=false
nachos=false
guppies=false

command
case $? in
    42) cheeseballs=true ;;
    17 | 31) cheeseballs=true; nachos=true; guppies=true;;
    66) guppies=true; echo "Bingo!";;
esac

$cheeseballs && java -crash -burn
$nachos && python ./tex.py --mex
if $guppies; then
    aquarium --light=blue --door=hidden --decor=squid
else
    echo SRY
fi

正如@CharlesDuffy在评论中指出的那样,在变量中存储一个实际的命令有点可疑,并且模糊地触发Bash FAQ #50警告;代码更自然地读取(略微和恕我直言),但你必须非常小心,你始终可以完全控制变量。如果您有任何疑问,可能只需使用字符串值并与每个交汇点的预期值进行比较。

[ "$cheeseballs" = "true" ] && java -crash -burn
等等;或者你可以重构布尔值的其他实现结构(一个关联的选项数组是有意义的,但不能移植到POSIX sh;一个PATH - 像字符串是灵活的,但也许也是非结构化)。

答案 2 :(得分:2)

如果您不需要存储特定的退出状态,只需要命令成功还是失败(例如grep是否找到匹配项),我使用fake boolean variable来存储结果:< / p>

if long_running_command | grep trigger_word; then
    found_trigger=true
else
    found_trigger=false
fi

# ...later...
if ! $found_trigger; then
    # stuff to do if the trigger word WASN'T found
fi

#...
if $found_trigger; then
    # stuff to do if the trigger WAS found
fi

注意:

  • shell实际上没有布尔(true / false)变量。这里实际发生的是“true”和“false”作为字符串存储在found_trigger变量中;当if $found_trigger; then执行时,它会将$found_trigger的值作为命令运行,而true命令总是成功,false命令总是失败,从而导致“正确的事情“要发生。在if ! $found_trigger; then中,“!”切换成功/失败状态,有效地充当布尔“不”。
  • if long_running_command | grep trigger_word; then相当于运行命令,然后使用if [ $? -ne 0 ]; then检查其退出状态。我发现它更清洁,但你必须习惯于将if视为检查命令的成功/失败,而不仅仅是测试布尔条件。如果“主动”if命令对您来说不直观,请改为使用单独的测试。
  • 正如Charles Duffy在评论中指出的那样,这个技巧将数据作为命令执行,如果你没有对这些数据的完全控制......你无法控制你的脚本将会发生什么去做。因此,永远不要将伪布尔变量设置为除固定字符串“true”和“false”之外的任何内容,并确保在使用之前设置变量。如果脚本中有任何重要的执行流程,请在执行流程变得复杂之前,将所有假布尔变量设置为合理的默认值(即“true”或“false”)

    如果不遵守这些规则,可能会导致安全漏洞大到足以驱动货运列车通过。

答案 3 :(得分:2)

基于OP的澄清,它只是关于成功与失败(而不是具体的退出代码):

long_running_command | grep -q trigger_word || failed=1

if ((!failed)); then
  : stuff
else

: more code

if ((!failed)); then
  : stuff
else
  • 仅在失败上设置成功指标变量(通过||,即,如果返回非零退出代码)。
  • 依赖于未定义的变量在算术条件(( ... ))中评估为 false 的事实。
  • 必须注意变量(在此示例中为$failed)未在其他地方意外初始化。

(旁注,正如@nos在评论中已经提到的,你需要小心涉及管道的命令;来自man bash(强调我的):

  

管道的返回状态是最后命令的退出状态,   除非启用了pipefail选项。如果启用了pipefail,则   管道的返回状态是最后(最右边)命令的值   以非零状态退出,如果所有命令都成功退出则返回零。

要设置pipefail(默认为OFF),请使用set -o pipefail;要将其关闭,请使用set +o pipefail。)

答案 4 :(得分:1)

如果您不关心确切的错误代码,可以这样做:

if long_running_command | grep -q trigger_word; then
    success=1
    : success
else
    success=0
    : failure
fi

if ((success)); then
    : success
else
    : failure
fi

0用于false,将1用于true是我在脚本中存储布尔值的首选方式。 if ((flag))很好地模仿C.

如果你关心退出代码,那么你可以这样做:

if long_running_command | grep -q trigger_word; then
    status=0
    : success
else
    status=$?
    : failure
fi

if ((status == 0)); then
    : success
else
    : failure
fi

我更喜欢针对0的显式测试,而不是使用!,这是不正确的。

(是的,$?确实在这里产生了正确的值。)

答案 5 :(得分:-1)

嗯,问题有点模糊 - 如果可能的话,我建议考虑重构/简化,即

function check_your_codes {
# ... run all 'checks' and store the results in an array
}
###
function process_results {
# do your 'stuff' based on array values
}
###
create_My_array
check_your_codes
process_results

此外,除非您确实需要保存退出代码,否则无需 store_and_test - 只需 test_and_do ,即使用上述建议的案例陈述或类似:

run_some_commands_and_return_EXIT_CODE_FROM_THE_LAST_ONE
if [[ $? -eq 0 ]] ; then do_stuff else do_other_stuff ; fi

:)
Dale