我的 bash 知识有点生疏(之前也不是很扎实),所以我似乎无法找到以下问题的答案:
正如标题所说,我想知道如何确定命令执行后是否由 bash 设置了非零退出代码(意味着真实的错误)或通过命令(可能指示错误,取决于命令和我的目的)。
例如,让我们看一下以下非常简单的脚本:
#!/bin/bash
string='abc'
grep 'd' <<< "$string"
echo $?
这输出 1,这是在阅读了grep
手册(摘录,缩短我的)后所期望的:
退出状态
通常,如果选择了一行,则退出状态为 0;如果没有选择任何行,则退出状态为 1;如果发生错误,则退出状态为 2。 [...]
bash
在阅读了手册的相应部分后,我遇到了一个问题(我的摘录、缩短和强调):
退出状态
[...]
如果未找到命令,则为执行该命令而创建的子进程将返回状态 127。如果找到命令但不可执行,则返回状态为 126。
如果命令由于扩展或重定向期间的错误而失败,则退出状态大于零。
如果成功,Shell 内置命令将返回状态 0 (true);如果执行时发生错误,则返回非零 (false)。所有内置函数都会返回退出状态 2 来指示不正确的使用、通常无效的选项或缺少参数。
[...]
我的问题是强调的陈述。
grep
我的脚本通常需要专门处理真正的错误(例如缺乏权限、所需的程序不可用、资源耗尽等),但在上面的示例中,当不选择一行时,这不是上述意义上的错误;相反,它仅意味着其输入不包含匹配的字符序列。
bash
但是,如果我从字面上理解 手册中的部分,则可能是bash
它本身设置了 的退出状态1
。从该部分,我们知道如果无法找到命令(退出状态127
)或不可执行命令(退出状态126
)会发生什么。
据我了解,该部分中的下一个语句意味着每个其他错误都可以[1, 255]
通过 bash 映射到包含范围内的任何退出状态。值得注意的是,它可以映射到退出状态1
。我正在考虑这是一个主要问题,因为我相信除了“找不到命令”或“命令无法执行”之外,还存在大量错误。例如,命令执行可能会因内存耗尽、文件句柄耗尽、磁盘读取错误导致的超时等而被阻止。
与“grep 找不到匹配的行”错误相比,这些是真实的严重错误,通常会导致向管理员发送电子邮件以立即采取措施。
但现在看来我无法区分两种错误(执行命令设置的非零退出状态与尝试执行命令后 bash 设置的非零退出状态)。
有人能给我指出一个合理的解决方案吗?
类似问题
在我的研究过程中,我遇到了很多类似的问题。然而,据我所知,没有人拥有精确的同样的问题。
相反,大多数人只是想要抑制命令返回的非零退出代码(应用于我的示例,他们希望有退出状态0
,而不是1
没有grep
选择行时的状态),并得到了类似于 的解决方案command || true
。
虽然这对他们来说可能是可以接受的,但对我来说这不是一个解决方案,因为它也会抑制真实的上面提到的错误。例如,请考虑以下情况:
root@cerberus:~/scripts# { ThisProgramDoesNotExist 2>/dev/null || true; } && { echo "Gotcha!"; }
Gotcha!
root@cerberus:~/scripts#
这演示了该解决方案如何不仅抑制执行命令的非零退出状态(或者是“stati”?),而且抑制 bash 在启动命令失败时报告的严重错误。这在我的大多数脚本中是不允许的。
答案1
你说不出来。您得到的只是 0 到 255 之间的单个值,如果一切顺利则为 0,否则为非零。
如果您想将某些非零状态视为成功,请确保相关命令不会因其他原因(例如重定向)而失败。分解命令,以便不同类型的故障发生在不同的命令中或导致不同的状态。
例如,如果您需要知道错误是否来自重定向,则可以在单独的命令中执行重定向,或者通过块单独执行重定向。
综合状态:
mycommand <foo
status=$?
if [ $status -ne 0 ]; then echo "Either mycommand failed or <foo failed"; fi
单独的状态,但如果重定向失败,则无法避免运行该命令:
{
mycommand
command_status=$?
} <foo
redirection_status=$?
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
if [ $redirection_status -ne 0 ]; then echo "<foo failed"; fi
首先进行重定向。请注意,能够以这种方式对重定向失败做出反应是 bash 的一项功能。如果内置重定向exec
失败,POSIX shell(包括 POSIX 模式下的 bash)将退出。
exec 3<&1 # Save stdin to file descriptor 3
exec <foo # bash keeps going if the redirection fails
redirection_status=$?
mycommand
command_status=$?
exec <&3 # Restore stdin
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
if [ $redirection_status -ne 0 ]; then echo "<foo failed"; fi
重定向是否失败,在子 shell 中包含重定向失败,而不必在事后恢复文件描述符。
(
exec <foo || exit $? # In POSIX sh, "|| exit $?" is redundant.
mycommand
command_status=$?
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
)
redirection_status=$?
if [ $redirection_status -ne 0 ]; then echo "<foo failed and mycommand didn't run"; fi
如果您需要知道错误是否来自其他扩展,请单独进行扩展并保存其结果。
保存一个参数:首先保存扩展的结果,而不是 `mycommand "$(…)"。
foo=$(…) && mycommand "$foo"
更普遍:
foo=$(…)
command_substitution_status=$?
mycommand "$foo"
mycommand_status=$?
请注意,如果分配包含多个命令替换,则其状态为最后一次替换的状态:如果最后一次替换成功,则状态为 0,即使之前的替换失败。
foo=$(…)
foo_status=$?
bar=$(…)
bar_status=$?
mycommand "$foo" "$bar"
mycommand_status=$?
要保存多个参数,请使用数组或函数内的位置参数。
args=()
foo=$(…)
foo_status=$?
args+=(-x "$foo")
bar=$(…)
bar_status=$?
args+=(-y "$bar")
mycommand "${args[@]}"
答案2
运行该命令后,我不知道有任何区分这种差异的方法。但如果您接受竞争条件,那么可能会有足够的选择。
代替
cmd with "$params" and </re/di/rections
你做
if : with "$params" and </re/di/rections; then
# expansion and redirections are OK
# let's hope no redirection-relevant paths are deleted or created in the meantime
# and, of course, this does not work well with noclobber
cmd with "$params" and </re/di/rections
else
: error "outside" the command
fi