为啥要测试“$”?查看命令是不是成功,反模式?

Posted

技术标签:

【中文标题】为啥要测试“$”?查看命令是不是成功,反模式?【英文标题】:Why is testing "$?" to see if a command succeeded or not, an anti-pattern?为什么要测试“$”?查看命令是否成功,反模式? 【发布时间】:2016-07-18 17:10:09 【问题描述】:

我看到here 那个测试是否 $?为零(成功)或其他(失败)是反模式,但我无法在其他任何地方找到它。

坚持Wikipedia 对反模式的定义:“反模式(或反模式)是对反复出现的问题的常见反应,通常是无效的,并且可能会适得其反。”为什么这是一个反模式?

【问题讨论】:

“通常无效”是该定义中经过深思熟虑的组成部分。如果有一个最佳实践替代方案可以解决一个主要的陷阱或警告,而没有任何负面的权衡,那么在超过 50% 的时间工作时,某些东西可能是一种反模式。事实上,有几件事是反模式,因为它们引入了安全风险。一个安全漏洞只需要被利用一次来使具有该漏洞的代码不可接受,而不是超过 50% 的执行。 ...除非它在大多数中表示“通常”,但不是全部,反模式的使用往往是无效的;这更模糊,因此更难以争议。 见BashPitfalls #44 (cmd; (( ! $? )) || die)。 【参考方案1】:

这是一种反模式,因为它引入了如果您根本不需要记录退出状态就不会存在的复杂性。

if your_command; then ...

出错的可能性比

少得多
your_command
if [ "$?" -eq 0 ]; then ...

对于可能出错的事情的示例:想想traps,甚至为调试添加新的echo 语句,修改$?。在不改变逻辑流程的情况下,运行your_command 的单独行不能在其下方添加任何内容,这对读者来说在视觉上并不明显。

即:

your_command
echo "Finished running your_command" >&2
if [ "$?" -eq 0 ]; then ...

...正在检查 echo,而不是实际的命令。


因此,如果您确实确实需要以一种比立即判断其值是否为零更精细的方式处理退出状态,您应该在同一行收集它:

# whitelisting a nonzero value for an example of when "if your_command" won't do.
your_command; your_command_retval=$?
echo "Finished running your_command" >&2 ## now, adding more logging won't break the logic.
case $your_command_retval in
  0|2) echo "your_command exited in an acceptable way" >&2;;
  *)   echo "your_command exited in an unacceptable way" >&2;;
esac

最后:如果您将your_command 包含在if 语句中,这会将其标记为已测试,这样您的shell 就不会考虑set -e 的非零退出状态或ERR 陷阱。

因此:

set -e
your_command
if [ "$?" -eq 0 ]; then ...

...永远不会(除了一些困扰set -e 行为的极端情况和警告) 到达if 的任何$? 值而不是@987654337 的语句@,因为在这种情况下,set -e 将强制退出。相比之下:

set -e
if your_command; then ...

...将your_command 的退出状态标记为已测试,因此不认为它会导致根据set -e 强制脚本退出。

【讨论】:

有趣。但只有当它用作二进制/布尔值时,它才是反模式 @KevinDTimm,确实如此。语言功能有用;在不需要时使用它是不受欢迎的。 @KevinDTimm, ...并且标题明确调用了“查看命令是否成功”的用例——如果该命令遵循零/非零退出状态约定,则确定是否它成功真的是一个布尔决定。 在编写软件这么多年之后,我不会想到使用不正确的形式,除非在早期开发工作中我对调试输出感兴趣。如前所述,不要增加不必要的复杂性。 @WilliamPursell,区分不同类型的故障有时是有意义和有用的——我在生产代码中确实有一些地方我想忽略错误 X 而不是错误 Y——同时需要检查 @ 987654342@ 不是干净的代码,它不像尝试检查写入 stderr 的文本那么讨厌(以及随之而来的脆弱性、本地化问题等)【参考方案2】:

如果您不需要命令的返回值而只需要检查状态,我认为这是一种反模式。

如果我们需要函数返回值和退出代码,没有其他方法(或者我不知道,如果我错了,请纠正我)。在下面的示例中,只有在上一个命令成功并且我们在其他函数中使用返回值时,我们才需要继续

例子:

test()  echo "A" ; return 1; 
l_value=$(test); l_exit_code=$?;

if (( l_exit_code == 0 )); then
   fn_other_function_to_pass_value $l_value
else
   echo "ERROR" >&2
   exit 1
fi

【讨论】:

还有另一种方法。你可以做if l_value=$(test); then ...。仅由赋值语句组成的简单命令的退出状态是最后替换的命令的退出状态(即所有现代 shell 中最右边的 $(...)),如果没有替换命令,则为 0。例如,a=b 的退出状态为 0,a=$(exit 1) 为 1,a=$(exit 1)$(exit 2) 为 2,a=$(exit 1) b=$(exit 2) 为 2。对于重定向时这些命令的退出状态应该是什么,shell 之间没有达成共识参与其中(例如a=$(exit 1) >$(echo /dev/null))。 感谢您的解释

以上是关于为啥要测试“$”?查看命令是不是成功,反模式?的主要内容,如果未能解决你的问题,请参考以下文章

ubuntu为啥克隆不到mininet

为啥“log and throw”被认为是一种反模式? [关闭]

如何检查nginx安装是不是成功

怎么查看mysql是不是安装成功

汇编数据段地址问题 看我的源代码,从反汇编的代码中可以看到段地址DS应该为075A 但是D命令查看的结果不是

如何测试mysql是不是安装成功