每次命令跑完,为什么敲 echo $? 总能看到一个数字? 0 代表成功,这个我知道,可为什么失败的时候经常是 1、2,有时候是 126、127,甚至 130、137? 这些数字到底在说什么?为什么 shell 要用这么一套“暗号系统”?
echo $?
我花了点时间自己捋了一遍,越挖越觉得有意思,今天写下来跟大家分享。
简单说:
$? 保存的是“上一条命令的退出状态码”(exit status)
最让人意外的一点是:为什么偏偏用 0 表示成功,而不是像我们平常思维那样用 1 表示成功?
其实答案藏在失败的多样性里。
成功通常只有一种状态:「干成了」。 但失败可以有无数种原因:文件不存在、权限不够、语法错误、参数不对、网络超时、被信号杀死……
如果我们约定 1 = 成功,0 = 失败,那么失败就只能用一个数字 0 来表达,所有失败看起来都一样,程序就没办法告诉你“我到底是哪一种失败”。
反过来,把唯一最特别、最容易记住的数字 0 留给“成功”,把 1~255 都留给各种失败,就有了足够的空间让程序“描述”自己遇到的具体问题。
这其实是 Unix 哲学里一个很经典的设计取舍:让最常见、最重要的状态用最简单的方式表达。
我们来实际看几个例子(你可以开个终端跟着试):
ls /tmp
ls /不存在的目录
grep "hello" 文件存在但没找到内容
grep "hello" 不存在的文件
command_not_found
./script.sh
你会发现:
理解了 $? 是什么之后,真正能提升脚本健壮性的,是学会怎么“响应”它。
下面是我最近用得最多的几种写法,按从简单到复杂的顺序排列:
不允许失败,立刻停止
cp important.txt /backup/ || exit 1
cp 失败 → 立刻退出脚本,返回 1
有依赖关系,一步成功才做下一步
mkdir -p backup && cp important.txt backup/
或者更长的链:
git pull && npm install && npm run build
整条链全部成功才算成功
command1 && command2 && command3 echo "全部成功"
中间任何一步失败,后面的都不执行,最后的 echo 也不会打印。
失败时需要做点额外的事(打印、清理、发通知)
if ! cp file.txt /backup/; then echo "复制失败了!请检查磁盘空间或权限" exit 2 fi
最显式、最容易理解的写法(适合复杂场景)
some_command if [ $? -ne 0 ]; then echo "上一步出错了,错误码:$?" # 可以在这里加 rm、logger、邮件通知等 exit 1 fi
简洁但能做多件事的写法(个人最近最爱)
some_command || { echo "出错了!" rm -f temp.* exit 1 }
写 shell 脚本的时候,最贵的不是代码多写几行,而是“错了还继续执行”导致的灾难。
学会善用 $? + && + || + {},能让你的脚本从“看起来能跑”变成“真的比较健壮”。
$?
&&
||
{}
下次写脚本的时候,不妨问自己一句:
“这一步失败了,我是允许继续,还是应该立刻停下来报警?”
希望这篇小文能帮到正在折腾 shell 的你。
深入简出,好帖
好贴支持
Featured Collection
Popular Events
最近在写 shell 脚本的时候,突然对一个东西特别好奇:
每次命令跑完,为什么敲
echo $?总能看到一个数字?0 代表成功,这个我知道,可为什么失败的时候经常是 1、2,有时候是 126、127,甚至 130、137?
这些数字到底在说什么?为什么 shell 要用这么一套“暗号系统”?
我花了点时间自己捋了一遍,越挖越觉得有意思,今天写下来跟大家分享。
第一层认知:$? 到底存的是什么?
简单说:
$? 保存的是“上一条命令的退出状态码”(exit status)
最让人意外的一点是:为什么偏偏用 0 表示成功,而不是像我们平常思维那样用 1 表示成功?
其实答案藏在失败的多样性里。
成功通常只有一种状态:「干成了」。
但失败可以有无数种原因:文件不存在、权限不够、语法错误、参数不对、网络超时、被信号杀死……
如果我们约定 1 = 成功,0 = 失败,那么失败就只能用一个数字 0 来表达,所有失败看起来都一样,程序就没办法告诉你“我到底是哪一种失败”。
反过来,把唯一最特别、最容易记住的数字 0 留给“成功”,把 1~255 都留给各种失败,就有了足够的空间让程序“描述”自己遇到的具体问题。
这其实是 Unix 哲学里一个很经典的设计取舍:让最常见、最重要的状态用最简单的方式表达。
第二层:最常见的几个退出码长什么样?
我们来实际看几个例子(你可以开个终端跟着试):
ls /tmpls /不存在的目录grep "hello" 文件存在但没找到内容grep "hello" 不存在的文件command_not_found./script.sh但没有执行权限你会发现:
第三层:写脚本时最常用的 6 种处理 $? 的姿势
理解了 $? 是什么之后,真正能提升脚本健壮性的,是学会怎么“响应”它。
下面是我最近用得最多的几种写法,按从简单到复杂的顺序排列:
不允许失败,立刻停止
cp 失败 → 立刻退出脚本,返回 1
有依赖关系,一步成功才做下一步
或者更长的链:
整条链全部成功才算成功
中间任何一步失败,后面的都不执行,最后的 echo 也不会打印。
失败时需要做点额外的事(打印、清理、发通知)
最显式、最容易理解的写法(适合复杂场景)
简洁但能做多件事的写法(个人最近最爱)
最后想说的一句话
写 shell 脚本的时候,最贵的不是代码多写几行,而是“错了还继续执行”导致的灾难。
学会善用
$?+&&+||+{},能让你的脚本从“看起来能跑”变成“真的比较健壮”。下次写脚本的时候,不妨问自己一句:
“这一步失败了,我是允许继续,还是应该立刻停下来报警?”
希望这篇小文能帮到正在折腾 shell 的你。