命令执行后,如何确定退出代码是由 bash 设置还是由执行的命令设置?

命令执行后,如何确定退出代码是由 bash 设置还是由执行的命令设置?

我的 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

相关内容