我的 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 在无法启动命令时报告的严重错误。这在我的大多数脚本中都是不可行的。
你说不出来。您得到的只是 0 到 255 之间的单个值,如果一切顺利则为 0,否则为非零。
\n如果您想将某些非零状态视为成功,请确保相关命令不会因其他原因(例如重定向)而失败。分解命令,以便不同类型的故障发生在不同的命令中或导致不同的状态。
\n例如,如果您需要知道错误是否来自重定向,请在单独的命令中执行重定向,或者通过块单独执行重定向。
\n综合状态:
\nmycommand <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
。
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)"。
\nfoo=$(\xe2\x80\xa6) && mycommand "$foo"\n
Run Code Online (Sandbox Code Playgroud)\n更普遍:
\nfoo=$(\xe2\x80\xa6)\ncommand_substitution_status=$?\nmycommand "$foo"\nmycommand_status=$?\n
Run Code Online (Sandbox Code Playgroud)\n请注意,如果分配包含多个命令替换,则其状态为最后一次替换的状态:如果最后一次替换成功,则状态为 0,即使之前的替换失败。
\nfoo=$(\xe2\x80\xa6)\nfoo_status=$?\nbar=$(\xe2\x80\xa6)\nbar_status=$?\nmycommand "$foo" "$bar"\nmycommand_status=$?\n
Run Code Online (Sandbox Code Playgroud)\n要保存多个参数,请使用数组或函数内的位置参数。
\nargs=()\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