设置errexit选项时检测shell退出代码的正确方法

时间:2012-03-09 06:10:37

标签: shell

我更喜欢编写solid shell 代码,因此errexit& nounset总是设定。

以下代码将在bad_command行停止

#!/bin/bash
set -o errexit ; set -o nounset
bad_command # stop here
good_command

我想抓住它,这是我的方法

#!/bin/bash
set -o errexit ; set -o nounset
rc=1
bad_command && rc=0 # stop here
[ $rc -ne 0 ] && do_err_handle
good_command

是否有更好或更清洁的方法

我的回答:

#!/bin/bash
set -o errexit ; set -o nounset
if ! bad_command ; then
  # error handle here
fi
good_command

13 个答案:

答案 0 :(得分:37)

这个怎么样?如果你想要实际的退出代码......

#!/bin/sh                                                                       
set -e

cat /tmp/doesnotexist && rc=$? || rc=$?                                         
echo exitcode: $rc        

cat /dev/null && rc=$? || rc=$?                                                 
echo exitcode: $rc   

输出:

cat: /tmp/doesnotexist: No such file or directory
exitcode: 1
exitcode: 0

答案 1 :(得分:18)

继续使用errexit。它可以帮助找到可能具有不可预测(并且难以检测)结果的错误。

#!/bin/bash
set -o errexit ; set -o nounset

bad_command || do_err_handle
good_command

以上工作正常。 errexit只需要传递一行,就像使用bad_command && rc=0一样。因此,上面带有'或'只会在bad_command失败时运行do_err_handle,并且只要do_err_handle也没有'失败',那么脚本将继续。

答案 2 :(得分:3)

set -o errexit

bad_command || {
  resp_code=$?
  echo Bad Thing $resp_code happened
}

答案 3 :(得分:3)

设置errexit并且运行可能失败的命令时,避免退出Bash程序的常用方法是在命令前加!

命令运行后$?不包含命令的退出状态。 (如果命令失败则包含0,否则包含1.)但是,PIPESTATUS数组确实包含命令的退出状态。无论是否设置errexit,捕获可能失败的命令的退出状态的安全方法是:

! bad_command
rc=${PIPESTATUS[0]}

第二行可以简化为rc=$PIPESTATUS,但是Shellcheck会抱怨它。

如果(通常是这种情况)你不需要知道命令的退出状态,只要它成功或失败,那么@george的解决方案是好的,如果错误处理程序是一行的。对于多行错误处理程序,一个很好的选择是:

if ! bad_command ; then
    # Handle errors here
fi

请注意(除非在非常特殊情况下)使用`bad_command`(如问题中所示)而不是普通bad_command 是正确的。如果错误处理代码中需要,您可以使用${PIPESTATUS[0]}来获取命令的退出状态,因为在这种情况下$?不包含它。

答案 4 :(得分:2)

同意评论,如果您可以放弃errexit,那么您可以轻松地将代码缩短为

 bad_command || do_err_handle
 good_command

我希望这会有所帮助。

答案 5 :(得分:2)

如果您要检测复合列表(或函数)的退出代码,并在其中应用errexitnounset,则可以使用以下代码:

#!/bin/sh
set -eu
unset -v unbound_variable

f() {
    echo "before false"
    false
    echo "after false"
}

g() {
    echo "before unbound"
    var=$unbound_variable
    echo "after unbound"
}

set +e
(set -e; f)
echo error code of f = $?
set -e

echo still in main program

set +e
(set -e; g)
echo error code of g = $?
set -e

echo still in main program

上面应该为函数fg打印非零错误代码。我想这可以通过任何POSIX shell进行。您还可以在EXIT陷阱中检测到错误代码,但此后外壳会退出。其他建议方法的问题在于,在测试此类复合列表的退出状态时,errexit设置将被忽略。这是POSIX standard的引文:

执行复合列表时,-e设置将被忽略 在while,if,if或elif保留字之后 开始于!保留字,或AND-OR列表的任何命令 除了最后一个。

请注意,如果您已将函数定义为

f() (
    set -e
    ...
)

这足以做到

set +e
f
echo exit code of f = $?
set -e

获取退出代码。

答案 6 :(得分:1)

只是想完成以下答案:https://stackoverflow.com/a/32201766/2609399 ,这是一个很好的捕获,顺便说一句。

我前段时间遇到过这个限制,并对其应用了类似的修复。请考虑以下代码示例。

invokeBashFunction() {
  local functionExitCode="0"
  /bin/bash -c "
    set -o errexit

    ${*}
  " || functionExitCode="${?}"

  # add some additional processing logic/code

  return "${functionExitCode}"
}
export -f invokeBashFunction

关于如何使用它的例子很少:

invokeBashFunction bad_function param1 "param 2" param3 && echo "It passed." || echo "It failed!"
if invokeBashFunction bad_function param1 "param 2" param3
then
  echo "It passed."
fi
if ! invokeBashFunction bad_function param1 "param 2" param3
then
  echo "It failed!"
fi

答案 7 :(得分:0)

如果你想知道bad_command的退出状态怎么办?

我认为最简单的方法是禁用errexit:

#!/bin/sh
set -o errexit

some_code_here

set +o errexit
bad_command
status=$?
set -o errexit
process $status

答案 8 :(得分:0)

bash 中你可以使用内置的陷阱,这是非常好的。看到 https://unix.stackexchange.com/questions/79648/how-to-trigger-error-using-trap-command

不确定它与其他炮弹有多便携..所以YMMV

答案 9 :(得分:0)

我拼凑了所有答案中的(希望)教科书示例:

#!/usr/bin/env bash

# exit immediately on error
set -o errexit

file='dfkjlfdlkj'
# Turn off 'exit immediately on error' during the command substitution
blah=$(set +o errexit && ls $file) && rc=$? || rc=$?
echo $blah
# Do something special if $rc
(( $rc )) && echo failure && exit 1
echo success

答案 10 :(得分:0)

@rrauenza给出的答案略有不同。由于&& rc=$?部分的答案将始终等于&& rc=0,因此您可以在运行命令之前将rc设置为0。我认为结果的最终可读性更高,因为该变量是在其自己的代码行中预先定义的,并且仅在命令以非零退出状态退出时才更改。如果还给出了nounset,那么现在可以清楚地知道rc的确从未定义过。这样也可以避免在同一行中混用&&||,这可能会造成混淆,因为人们可能并不总是心生地知道运算符的优先级。

#!/bin/sh                                                                       
set -eu

rc=0
cat /tmp/doesnotexist || rc=$?                                         
echo exitcode: $rc        

rc=0
cat /dev/null || rc=$?                                                 
echo exitcode: $rc   

输出:

cat: /tmp/doesnotexist: No such file or directory
exitcode: 1
exitcode: 0

答案 11 :(得分:0)

干净可靠的错误退出方式

command_that_error_exits || { echo "Line $LINENO: Failed with Error" 1>&2; exit 1;}

答案 12 :(得分:-1)

接受的答案是好的,但我认为可以重构得更好;更通用,更容易重构和阅读:

some_command_status=$(some_command && echo $? || echo $?)

VS

some_command && some_command_status=$? || some_command_status=$?