Shell 设置errexit选项时检测外壳程序退出代码的正确方法

Shell 设置errexit选项时检测外壳程序退出代码的正确方法,shell,Shell,我更喜欢编写solid shell代码,因此errexit&nounset总是设置好的 以下代码将在bad_命令行停止 #!/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

我更喜欢编写solid shell代码,因此errexit&nounset总是设置好的

以下代码将在bad_命令行停止

#!/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

同意注释,因此如果您可以放弃
errexit
,那么您可以轻松地将代码缩短到

 bad_command || do_err_handle
 good_command

我希望这能有所帮助。

请继续。它可以帮助发现可能产生不可预测(且难以检测)结果的bug


以上方法很好
errexit
只要求行通过,就像使用
bad_命令&rc=0
一样。因此,如果
bad\u命令失败,上面带有“or”的命令只会运行do\u err\u handle,并且只要do\u err\u handle没有“fail”,那么脚本将继续运行。

如果您想知道bad\u命令的退出状态,该怎么办

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

#!/bin/sh
set -o errexit

some_code_here

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

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

#!/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
cat: /tmp/doesnotexist: No such file or directory
exitcode: 1
exitcode: 0
set-o errexit 错误的命令{ 响应代码=$? echo坏事情$resp_代码发生 }
继续编写实心shell代码

如果bad_命令真的是一个命令,那么您就做对了。 但是在if、while、| |、&&or!中调用函数时要小心!,因为errexit在那里不起作用。这可能很危险

如果bad_命令实际上是bad_函数,则应编写以下代码:

set -eu 

get_exit_code() {
    set +e
    ( set -e;
      "$@"
    )
    exit_code=$?
    set -e
}

...

get_exit_code bad_function

if [ "$exit_code" != 0 ]; then
    do_err_handle
fi
这在Bash4.0中运行良好。在bash 4.2中,您只能获得退出代码0或1。

在bash中,您可以使用内置的陷阱,这非常好。看见


不确定它与其他外壳的可移植性。。所以YMMV

被接受的答案是好的,但我认为它可以被重构成更好的;更通用、更易于重构和阅读:

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


当设置了
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]}
获取该命令的退出状态,因为在这种情况下
$?
也不包含该命令。

我从所有答案中拼凑了一个(希望如此)教科书示例:

#!/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

@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
cat: /tmp/doesnotexist: No such file or directory
exitcode: 1
exitcode: 0

一种干净可靠的错误退出方法

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

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

#!/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
上面应该为函数
f
g
打印非零错误代码,尽管您可能希望脚本在未绑定变量错误后立即退出。我想这适用于任何POSIX shell。您还可以在退出陷阱中检测错误代码,但shell随后退出。其他建议方法的问题是,当测试此类复合列表的退出状态时,
errexit
设置被忽略。以下是一段引用自:

执行复合列表时,应忽略-e设置 在while之后,直到、if或elif保留字,一个管道 从最开始!保留字,或AND-or列表的任何命令 除了最后一个

请注意,如果您定义了如下函数

f() (
    set -e
    ...
)
这就足够了

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

获取退出代码。

只需完成以下回答: ,顺便说一句,这是一个很好的收获

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

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!"

如果要处理错误,请不要设置
errexit
。如果要处理错误,请处理错误。设置
errexit
意味着放弃了正确处理错误的责任。我从未发现有必要使用
名词集
,但我在拼写方面相当出色。即使要处理错误,也要始终使用errexit。任何外部命令都可能失败,除非您计划为| a&&work |流中的每个|命令添加错误处理,否则errexit至少有助于保护您和用户。与NouneSet相同,不使用它就像不在其他语言中使用assert一样(因为我从不出错,永远…)。仅供参考,所有这些都无法捕获全部errexit代码。如果您的
bad_命令
在内部调用另一个命令,例如
bad_com2
,t
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