退出代码:小整数背后的大含义
Unix工具和命令行界面(CLI)通常以一系列退出代码结束运行,这些代码意味着什么?为什么它们很重要?本文深入探讨每一个细节。
什么是退出代码?
退出代码只是“整数”(范围从0到255)。你可以将它们视为终端计算的HTTP状态码。顾名思义,退出代码为你提供有关操作状态的更多元数据,补充了stderr或stdout中的任何内容。
通常,在你选择的shell中,你可以通过运行以下命令找到先前退出命令的退出代码:
|
|
为什么这有效?
当在shell中运行命令时,shell进程调用fork()
创建一个新的子进程,子进程然后调用exec()
系列命令将自己替换为实际命令。每当子进程退出时,它会调用exit()
并带有一个状态码。这个状态码通过wait()
提供给shell(父进程)。shell然后将接收到的退出代码设置到一个名为$?
的特殊变量中。
常见退出代码
让我们来看看常见的退出代码,并为了有趣,我将它们分为几个“区域”。
“安全”区域
退出代码0通常意味着一切顺利。命令行程序以你预期的后果退出。换句话说,它是“成功”。
“不太安全”区域
正如你所推测的,非零状态码表示发生了错误。有些简单,有些更复杂,但它们都为我们提供了有价值的信息。
- 通用错误:这相当于捕获所有错误,告诉我们命令没有成功。
- 错误用法:不正确的输入或向CLI命令传递错误的标志将导致此错误。它很有用,因为它告诉我们进程甚至没有启动。可以将其视为客户端错误,HTTP 400听起来熟悉吗?
“可配置”区域
从这里开始,我们直接跳到126。等等!其他代码发生了什么?这就是真正的可配置性发挥作用的地方。每个CLI或Unix工具都可以自由记录并以3到125之间的任何退出代码退出,为确定进程退出的根本原因提供了极大的粒度。这不是很整洁吗?
“令人挠头”区域
- 126:命令存在,但无法运行。通常,这表示文件权限问题或需要超级用户访问权限。
- 127:(HTTP 404)命令不存在!所有你意外的拼写错误都会在这里结束。
提示:尝试在终端上输入rat
而不是cat
,并查看退出代码。
“信号”区域
事情开始变得真正有趣。记住,内核总是可以向运行中的进程发送信号。通常,进程有代码来处理信号(如果定义了信号处理程序)并相应地做出反应。让我们看几个例子。
128 + N是处理信号的进程返回的退出代码。这使得区分自行退出的进程与与信号交互的进程更容易。
- 130 - (128+2) SIGINT,通常这意味着进程必须处理来自终端的Ctrl + C。
- 137 - (128+9) SIGKILL,无法处理或忽略。在命令行上使用过
kill -9
的人会立刻认出这个。注意:这也可能由OOM杀手触发,稍后详述。 - 139 - (128+11) SIGSEGV,进程在尝试访问无效内存时被内核杀死。
- 143 - (128+15) SIGTERM,一个优雅的关闭,进程被允许清理、关闭打开的文件描述符等。
核心转储
现在,取决于ulimit设置。上述一些退出代码还会产生一个核心转储文件,然后可以用调试器逐帧分析以找出问题所在。这些文件可能非常大,取决于被杀死进程的大小。
“致命”区域
255:发生了真正致命的事情,在Unix系统中用作“捕获所有”退出代码。exit(-1)
也会以255退出。
退出代码标准
不同的Unix工具和CLI有自己的退出代码标准。最好检查man页面,它通常将“EXIT STATUS”作为自己的部分列出以找到 quirks。例如:grep
实际上认为没有匹配项是退出代码1,即使技术上它做得很好。对于自定义CLI来说,这变得更加棘手,我建议阅读现有CLI的文档,或者如果你正在构建自己的CLI,则提出一组清晰的退出代码。
为什么退出代码很重要?
退出代码有助于推动当今世界的各种现代自动化。
链式操作
|
|
每个步骤仅在前一个进程以0退出时成功。
|
|
CI/CD
所有持续集成运行器,如Github Actions、AWS Codebuild,都完全依赖退出代码来管理其构建管道。它们在非零退出代码时退出并记录错误。重要的是,对于自定义CLI工具,如果CLI的新版本对于完全相同的命令以与之前不同的退出代码退出,可能会造成瘫痪。这些回归会停止使用该CLI的消费者的世界。
监控
特定的退出代码,如137(OOM杀手),可以由监督进程监控,这些进程重新启动它们拥有的进程(例如:工作进程),从而提高服务的可用性。它也可以作为查找内存泄漏的地方,如果有核心转储。对于像ssh这样的工具,这还有一个安全角度,它记录255表示登录失败,并可以作为审计攻击的地方。
其他世界:太空中的退出代码
退出代码在太空中的远程调试中至关重要。火星精神探测器运行在VxWorks上,它使用sigLib返回0或-1并带有详细的错误号。NASA工程师能够基于重复记录的非零退出代码发现探测器陷入“重启”循环。这最终被追踪到内存问题。
对于更接近家庭的例子,我建议阅读这个。
结论
退出代码占用很小的空间,然而了解它们的含义可能在你下一次调试会话或自动化工作流程中至关重要。获得对它们的清晰性、控制和信心,你将成为终端向导。你最奇怪的退出代码故事是什么?