[Share Experiences] Linux 中那个神秘的 `$?` 到底是什么?从 0 到 130 的故事
Tofloor
poster avatar
babyfengfjx
Super Moderator
CQA
4 hours ago
Author

最近在写 shell 脚本的时候,突然对一个东西特别好奇:

每次命令跑完,为什么敲 echo $? 总能看到一个数字?
0 代表成功,这个我知道,可为什么失败的时候经常是 1、2,有时候是 126、127,甚至 130、137?
这些数字到底在说什么?为什么 shell 要用这么一套“暗号系统”?

我花了点时间自己捋了一遍,越挖越觉得有意思,今天写下来跟大家分享。

第一层认知:$? 到底存的是什么?

简单说:

$? 保存的是“上一条命令的退出状态码”(exit status)

  • 0 → 绝大多数情况下代表「成功 / 一切正常」
  • 非 0 → 代表「出问题了」,而且不同的数字往往想表达不同种类或不同严重程度的失败

最让人意外的一点是:为什么偏偏用 0 表示成功,而不是像我们平常思维那样用 1 表示成功?

其实答案藏在失败的多样性里。

成功通常只有一种状态:「干成了」。
但失败可以有无数种原因:文件不存在、权限不够、语法错误、参数不对、网络超时、被信号杀死……

如果我们约定 1 = 成功,0 = 失败,那么失败就只能用一个数字 0 来表达,所有失败看起来都一样,程序就没办法告诉你“我到底是哪一种失败”。

反过来,把唯一最特别、最容易记住的数字 0 留给“成功”,把 1~255 都留给各种失败,就有了足够的空间让程序“描述”自己遇到的具体问题。

这其实是 Unix 哲学里一个很经典的设计取舍:让最常见、最重要的状态用最简单的方式表达

第二层:最常见的几个退出码长什么样?

我们来实际看几个例子(你可以开个终端跟着试):

命令示例 常见 $? 值 含义解读(非官方,只是常见约定)
ls /tmp 0 成功
ls /不存在的目录 2 严重错误:文件/目录不存在、语法错误、坏选项等
grep "hello" 文件存在但没找到内容 1 逻辑失败:我正常运行了,但没找到你要的东西
grep "hello" 不存在的文件 2 更严重的错误:连文件都打不开
command_not_found 127 shell 找不到这个命令
./script.sh 但没有执行权限 126 找到了文件,但不能执行
运行中按 Ctrl+C 130 被 SIGINT 信号终止(128 + 2)
被 kill -9 杀死 137 被 SIGKILL 强制杀死(128 + 9)

你会发现:

  • 很多命令把 1 留给“逻辑上没问题,但结果是否定的”
  • 2 留给“命令本身用错了、环境有严重问题”
  • 127、126 是 shell 自己加的“特殊标记”
  • 128+ 是“被信号杀死”的专用区间(避免和程序自己 exit 的值冲突)

第三层:写脚本时最常用的 6 种处理 $? 的姿势

理解了 $? 是什么之后,真正能提升脚本健壮性的,是学会怎么“响应”它。

下面是我最近用得最多的几种写法,按从简单到复杂的顺序排列:

  1. 不允许失败,立刻停止

    cp important.txt /backup/ || exit 1
    

    cp 失败 → 立刻退出脚本,返回 1

  2. 有依赖关系,一步成功才做下一步

    mkdir -p backup && cp important.txt backup/
    

    或者更长的链:

    git pull && npm install && npm run build
    
  3. 整条链全部成功才算成功

    command1 && command2 && command3
    echo "全部成功"
    

    中间任何一步失败,后面的都不执行,最后的 echo 也不会打印。

  4. 失败时需要做点额外的事(打印、清理、发通知)

    if ! cp file.txt /backup/; then
        echo "复制失败了!请检查磁盘空间或权限"
        exit 2
    fi
    
  5. 最显式、最容易理解的写法(适合复杂场景)

    some_command
    if [ $? -ne 0 ]; then
        echo "上一步出错了,错误码:$?"
        # 可以在这里加 rm、logger、邮件通知等
        exit 1
    fi
    
  6. 简洁但能做多件事的写法(个人最近最爱)

    some_command || {
        echo "出错了!"
        rm -f temp.*
        exit 1
    }
    

最后想说的一句话

写 shell 脚本的时候,最贵的不是代码多写几行,而是“错了还继续执行”导致的灾难

学会善用 $? + && + || + {},能让你的脚本从“看起来能跑”变成“真的比较健壮”。

下次写脚本的时候,不妨问自己一句:

“这一步失败了,我是允许继续,还是应该立刻停下来报警?”

希望这篇小文能帮到正在折腾 shell 的你。

Reply Favorite View the author
All Replies
tacat
deepin
4 hours ago
#1

深入简出,好帖

Reply View the author
鲜衣怒马
deepin
4 hours ago
#2

agree 好贴支持

Reply View the author