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