退出码与错误处理

ShellBeginner
立即练习

简介

想象一下:每次你请别人完成一项工作,对方都会在完成后交给你一张小卡片。

  • 绿卡(0)表示「我已完美完成」。
  • 红卡(1-255)表示「出了问题」。

在 Linux 中,每条命令都会这样做。这称为退出码或退出状态。

  • 0:成功。
  • Not 0:失败。

除非主动查看,大多数情况下这些代码是不可见的,因此初学者经常忽略它们。专业脚本开发者会密切关注退出码,因为退出状态是 Shell 在命令和脚本之间报告成功或失败的主要方式之一。

这是 Unix 的核心设计模式:小型程序通过简单的约定彼此通信。文本可以通过标准输出传递,成功或失败则通过退出状态传递。这就是退出码在自动化任务中如此重要的原因。

在本实验中,你将学习如何使用变量 $? 读取这些秘密卡片,并在脚本出错前及时停止脚本。

使用 $? 检查命令状态

如何查看这个不可见的「成绩卡」?运行命令后,立即查看名为 $? 的特殊变量。

先查看命令成功时的情况。

打开终端,列出文件:

ls -l

命令成功执行了,对吧?现在查看「得分」:

echo $?

你应该会看到 0。按照 Linux 的逻辑,0 表示错误数量为 0。没有错误就表示完全成功。

现在强制制造一次失败。尝试列出不存在的文件:

ls ghosts.txt

Linux 会提示找不到该文件。现在再次查看得分:

echo $?

你会看到 2。它不是 0,因此表示失败。

具体的非零数字会因命令和情况而有所不同,但现在只需记住一个关键规则:0 表示成功,其他任何值都表示失败。之后使用脚本和工具时,你会看到有些程序会使用不同的非零退出码来表示不同类型的问题。

关键规则:$? 是短期记忆。它只记住你刚刚执行的最后一项操作。如果你先运行 ls(失败),再运行 echo "hello",最后查看 $?,你会看到 0,因为 echo 执行成功了!你必须立即检查 $?。

创建一个会失败的脚本

这里就是危险所在。默认情况下,脚本会忽略红卡,继续执行后续命令。

想象一个脚本执行以下操作:

  1. 创建备份。
  2. 删除原始文件。

如果第 1 步失败了(备份空间已满),第 2 步仍然会执行,你就会丢失所有文件。

现在模拟这个「盲目脚本」。

创建 copy_script.sh:

nano copy_script.sh

输入以下代码。它会尝试复制一个不存在的文件,然后声称操作成功。

#!/bin/bash

## Attempt to copy a ghost
cp ghost_file.txt destination.txt

## Print a happy message
echo "Script finished execution"

保存并退出。为脚本添加可执行权限:

chmod +x copy_script.sh

运行脚本:

./copy_script.sh

你会看到 cp 输出的错误信息,但随后又会看到「Script finished execution」。脚本虽然出错了,却仍然继续执行!

查看脚本本身的得分:

echo $?

它返回 0。为什么?因为最后一条命令(echo)执行成功了。这个脚本在说谎:它没有完成主要任务,却向系统报告了成功。

这是一个重要的经验:Shell 脚本也有退出状态。如果你不进行特殊处理,它通常会使用最后执行的命令的退出状态。因此,脚本可能会意外隐藏之前发生的错误。

实现错误处理

现在需要让脚本在遇到红灯时停止。

通常可以使用以下结构:「尝试执行命令 X。如果退出码不为 0,就立即处理错误。」

语法 [ $? -ne 0 ] 使用了 test 命令,这里采用方括号形式书写。-ne 表示数字「不等于」。因此,Shell 实际上是在询问:「上一条命令的退出状态是否不等于零?」

编辑脚本 copy_script.sh:

nano copy_script.sh

将内容修改为:

#!/bin/bash

cp ghost_file.txt destination.txt

## Check if the score is NOT equal (-ne) to 0
if [ $? -ne 0 ]; then
  echo "Error: The copy operation failed!"
  exit 1
fi

echo "Script finished execution"

代码说明:

  • if [ $? -ne 0 ]:检查退出码的条件。
  • exit 1:红色按钮。它会立即终止脚本,并将全局得分设为 1(错误)。

exit 命令会结束当前 Shell 脚本,并将指定的数字返回给启动它的对象,例如终端、其他脚本或自动化系统。换句话说,它不仅会在当前脚本中停止执行,还会向外部报告状态。

保存并退出。

再次运行脚本:

./copy_script.sh

脚本会输出错误信息并终止,不会输出「Script finished execution」。

查看得分:

echo $?

结果是 1。现在,脚本能够如实报告自己的失败状态。

总结

你已经学会了如何理解命令的秘密语言。

在本实验中,你练习了:

  1. 读取成绩卡:使用 $? 查看命令是成功(0)还是失败(1-255)。
  2. 揭穿谎言:了解脚本在处理不当时如何隐藏错误。
  3. 充当裁判:使用 exit 1,在发生错误时停止脚本。

这种「检查并退出」模式,是脆弱脚本与专业脚本之间的区别。