每个命令在结束时都会向操作系统返回一个数字,即 退出状态码(exit status)。这个简单的机制是 Shell 脚本判断命令成败、控制程序流程的基础。
1. 退出状态码的基本规则
- 0 表示成功:命令正常结束,没有发生任何错误。
- 非 0 表示失败:具体的非零值通常指示不同的错误类型,值范围在 1 到 255 之间。
例如,ls 列出一个不存在的目录时会返回 2;grep 找不到匹配行时返回 1;cp 如果源文件不存在或目标不可写也可能返回非 0。
在命令行中,上一个命令的退出状态码会保存在特殊变量 $? 中,可以直接查看:
ls /exist
echo $? # 输出 0
ls /nonexistent
echo $? # 输出 2
注意,$? 只保存最近一条命令的退出状态,执行任何新命令(包括 echo)后它就会被覆盖,因此如果需要多次使用,应当立即保存到一个变量里:
some_command
ret=$?
echo "返回值是 $ret"
2. 在脚本中据此判断成功与否
退出状态码最常见的用途是作为 if 语句或逻辑连接符的条件:
if command; then ... fi:if会根据command的退出状态(0 则真,非 0 则假)选择分支。&&和||:command1 && command2:仅当command1成功(退出码 0)时才执行command2。command1 || command2:仅当command1失败(非 0)时才执行command2。
例如:
mkdir -p /tmp/backup && cp file /tmp/backup/ && echo "备份成功"
wget -q "$url" || { echo "下载失败"; exit 1; }
在管道中,默认只有最后一个命令的退出状态会反映到 $?。如果需要检测管道中任意命令是否失败,可以使用 set -o pipefail(在 Bash 中),这样管道只要有一个命令失败,整体退出状态就是非 0。
3. 自定义脚本的退出码
在自己的脚本里,用 exit N 就可以让脚本以状态码 N 结束。如果没有显式调用 exit,脚本的退出状态就是最后一条执行命令的退出状态。为了更好地与系统其他部分协作,通常遵循约定:
- 用 0 表示正常完成;
- 用 1 表示一般性错误;
- 用其他数字(2-255)区分具体错误场景,例如 2 表示参数错误,3 表示文件不存在等。这样做便于外部调用者根据状态码采取不同动作。
4. 基本的错误处理模式
健壮的脚本不能假定每条命令都会成功。常见的实用处理方法:
(a)立即退出:set -e
在脚本开头加上 set -e,任何一条命令返回非 0 值时,脚本会立刻退出。这可以避免在失败状态下继续执行导致更严重的后果。需注意,有些命令的预期行为就可能返回非 0(如 grep 找不到匹配),可通过 || true 来排除:
set -e
grep "something" file || true # 即使找不到也继续
(b)检查关键命令并手动处理
对于重要步骤,显式检查 $? 是更清晰的方式:
backup_db
if [ $? -ne 0 ]; then
echo "数据库备份失败,请检查!" >&2
exit 5
fi
(c)使用 trap 捕获意外中断
可以用 trap 命令指定在脚本收到信号或退出时执行清理操作,例如删除临时文件:
trap "rm -f /tmp/workfile; echo '已清理'" EXIT
(d)输出错误信息到标准错误
当命令出错时,错误提示应该送往标准错误(stderr),而不是标准输出,以保持输出流的干净:
echo "错误:文件未找到" >&2
5. 常用工具的状态码惯例
虽然没有强制标准,但多数 GNU 工具遵循相似的语义:
grep:0(找到匹配),1(未找到),2(语法错误或文件问题)。diff:0(无差异),1(有差异),2(出错)。curl:成功时为 0,失败时多为 6(无法解析主机)等,具体查询手册。
了解这些惯例可以帮助你编写更准确的判断条件。
退出状态码看似简单,却是 Shell 脚本可靠性的基石。养成检查关键命令退出状态的习惯,善用 $?、set -e 和 || 处理异常,你的脚本会在实际运维环境中表现得更加稳固和可控。