命令执行后,如何判断是bash设置了退出码还是执行的命令设置了退出码?

Bin*_*rus 5 bash exit-status

我的 bash 知识有点生疏(以前也不是很扎实),所以我似乎无法找到以下问题的答案:

正如标题所说,我想知道如何确定命令执行后的非零退出代码是由 bash(表示真正的错误)还是由命令(可能表示错误,取决于命令)设置和我的目的)。

例如,让我们看看以下非常简单的脚本:

#!/bin/bash

string='abc'
grep 'd' <<< "$string"
echo $?
Run Code Online (Sandbox Code Playgroud)

这输出 1,这是在阅读grep's 手册后预期的(摘录,缩短我的):

退出状态
通常,如果选择了一行,退出状态为 0,如果没有选择任何行,则退出状态为 1,如果发生错误,则退出状态为 2。[...]

在阅读了bash's manual的相应部分后,我遇到了一个问题(摘录、缩短和强调我的):

退出状态

[...]

如果未找到命令,则为执行它而创建的子进程返回状态 127。如果找到命令但不可执行,则返回状态为 126。

如果命令因扩展或重定向期间的错误而失败,则退出状态大于零。

如果成功,Shell 内置命令将返回状态 0(真),如果在执行时发生错误,则返回非零(假)状态。所有内置函数都返回退出状态 2 以指示不正确的使用、通常无效的选项或缺少参数。

[...]

我的问题是强调语句。

我的脚本通常需要特别处理真正的错误(如权限不足、所需程序不可用、资源耗尽等),但在我上面的例子中,当grep不选择一行时,它不是上述意义上的错误; 相反,它只是意味着它的输入不包含匹配的字符序列。

但是,如果我从bash字面上看手册中的部分,则可能是bash它本身设置了1. 从该部分,我们知道如果找不到命令(退出状态127)或不可执行(退出状态126)会发生什么。

根据我的理解,该部分中的下一条语句意味着所有其他错误都可以[1, 255]通过 bash映射到包含范围内的任何退出状态。值得注意的是,它可以映射到 exit status 1。我认为这是一个主要问题,因为我相信除了“找不到命令”或“命令不可执行”之外还有大量错误。例如,内存耗尽、文件句柄耗尽、磁盘读取错误导致的超时等可能会阻止命令执行。

与“grep 找不到匹配的行”错误相反,这些是真正的严重错误,大多数情况下必须将电子邮件发送给管理员以立即采取行动。

但现在似乎我无法区分这两种错误(执行命令设置的非零退出状态与尝试执行命令后 bash 设置的非零退出状态)。

有人能给我指出一个合理的解决方案吗?

类似问题

在我的研究中,我遇到了很多类似的问题。但是,据我所知,没有人遇到完全相同的问题。

相反,大多数人只是想抑制由命令返回的非零退出代码(适用于我的示例,他们本来希望退出状态0而不是1何时grep未选择行),并获得了类似于command || true.

虽然这对他们来说可能是可以接受的,但对我来说这不是一个解决方案,因为它也会抑制上面提到的真正错误。例如,请考虑以下情况:

root@cerberus:~/scripts# { ThisProgramDoesNotExist 2>/dev/null || true; } && { echo "Gotcha!"; }
Gotcha!
root@cerberus:~/scripts#
Run Code Online (Sandbox Code Playgroud)

这表明该解决方案不仅可以抑制已执行命令的非零退出状态(或者它是“stati”?),还可以抑制 bash 在无法启动命令时报告的严重错误。这在我的大多数脚本中都是不可行的。

Gil*_*il' 1

你说不出来。您得到的只是 0 到 255 之间的单个值,如果一切顺利则为 0,否则为非零。

\n

如果您想将某些非零状态视为成功,请确保相关命令不会因其他原因(例如重定向)而失败。分解命令,以便不同类型的故障发生在不同的命令中或导致不同的状态。

\n

例如,如果您需要知道错误是否来自重定向,请在单独的命令中执行重定向,或者通过块单独执行重定向。

\n

综合状态:

\n
mycommand <foo\nstatus=$?\nif [ $status -ne 0 ]; then echo "Either mycommand failed or <foo failed"; fi\n
Run Code Online (Sandbox Code Playgroud)\n

单独的状态,但如果重定向失败,则无法避免运行该命令:

\n
{\n  mycommand\n  command_status=$?\n} <foo\nredirection_status=$?\nif [ $command_status -ne 0 ]; then echo "mycommand failed"; fi\nif [ $redirection_status -ne 0 ]; then echo "<foo failed"; fi\n
Run Code Online (Sandbox Code Playgroud)\n

首先进行重定向。请注意,能够以这种方式对重定向失败做出反应是 bash 的一项功能。POSIX shell,包括 POSIX 模式下的 bash,如果重定向则退出exec

\n
exec 3<&1         # Save stdin to file descriptor 3\nexec <foo         # bash keeps going if the redirection fails\nredirection_status=$?\nmycommand\ncommand_status=$?\nexec <&3          # Restore stdin\nif [ $command_status -ne 0 ]; then echo "mycommand failed"; fi\nif [ $redirection_status -ne 0 ]; then echo "<foo failed"; fi\n
Run Code Online (Sandbox Code Playgroud)\n

重定向是否失败,在子 shell 中包含重定向失败,而不必在事后恢复文件描述符。

\n
(\n  exec <foo || exit $?     # In POSIX sh, "|| exit $?" is redundant.\n  mycommand\n  command_status=$?\n  if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi\n)\nredirection_status=$?\nif [ $redirection_status -ne 0 ]; then echo "<foo failed and mycommand didn't run"; fi\n
Run Code Online (Sandbox Code Playgroud)\n

如果您需要知道错误是否来自其他扩展,请单独进行扩展并保存其结果。

\n

保存一个参数:首先保存展开的结果,而不是 `mycommand "$(\xe2\x80\xa6)"。

\n
foo=$(\xe2\x80\xa6) && mycommand "$foo"\n
Run Code Online (Sandbox Code Playgroud)\n

更普遍:

\n
foo=$(\xe2\x80\xa6)\ncommand_substitution_status=$?\nmycommand "$foo"\nmycommand_status=$?\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,如果分配包含多个命令替换,则其状态为最后一次替换的状态:如果最后一次替换成功,则状态为 0,即使之前的替换失败。

\n
foo=$(\xe2\x80\xa6)\nfoo_status=$?\nbar=$(\xe2\x80\xa6)\nbar_status=$?\nmycommand "$foo" "$bar"\nmycommand_status=$?\n
Run Code Online (Sandbox Code Playgroud)\n

要保存多个参数,请使用数组或函数内的位置参数。

\n
args=()\nfoo=$(\xe2\x80\xa6)\nfoo_status=$?\nargs+=(-x "$foo")\nbar=$(\xe2\x80\xa6)\nbar_status=$?\nargs+=(-y "$bar")\nmycommand "${args[@]}"\n
Run Code Online (Sandbox Code Playgroud)\n