echo $?紧跟在命令后运行,0 表示成功,非 0 表示失败。
约 1281 字大约 4 分钟
2026-05-14
命令行的每一行输入,背后都是同一个模型:Shell 把这行拆成"程序 + 选项 + 参数",启动这个程序,程序往三个数据流里读写,结束时留下一个退出码。把这个模型想清楚,你就能解释"为什么报错信息没被管道接住""为什么脚本判断成功失败要看 $?"。
理解一条命令是怎么被拆解执行的:程序名、选项、参数,标准输入 / 输出 / 错误三个流,以及退出码如何表达成功与失败。
echo $?紧跟在命令后运行,0 表示成功,非 0 表示失败。
cmd && echo ok&& 只在前一条退出码为 0 时执行后一条。
cmd || echo failed|| 只在前一条退出码非 0 时执行后一条。
cmd 2>/dev/null把 stderr 重定向到空设备,只保留正常结果。
当你输入这一行:
ls -lh --color=auto /var/logShell 会把它按空格拆成几段:
ls —— 程序名,Shell 会去 PATH 列出的目录里找这个可执行文件-lh、--color=auto —— 选项,改变程序的行为/var/log —— 参数,程序要处理的对象选项和参数的顺序大多数命令不敏感,但 -- 之后的内容一律当作参数。这在文件名以 - 开头时很有用:rm -- -weird-file.txt。
同一个功能往往有两种写法:
| 形式 | 例子 | 适合场景 |
|---|---|---|
| 短选项 | ls -l -a -h 或合并 ls -lah | 交互时手快 |
| 长选项 | ls --all --human-readable | 写进脚本,半年后还看得懂 |
交互敲命令用短选项无所谓,但脚本里优先用长选项——--human-readable 比 -h 自解释得多,代码审查时不用查手册。
每个程序运行时都自带三个流:
| 流 | 编号 | 装什么 |
|---|---|---|
| 标准输入 stdin | 0 | 程序读取的输入 |
| 标准输出 stdout | 1 | 正常结果 |
| 标准错误 stderr | 2 | 错误信息、诊断、进度提示 |
关键点:stdout 和 stderr 是分开的两条流。这解释了一个常见现象——
ls /nonexistent | grep foo报错信息 No such file or directory 还是会出现在屏幕上,因为它走的是 stderr,而管道 | 只接 stdout。要把错误也一起处理,得显式合并:
ls /nonexistent 2>&1 | grep foo反过来,如果你想要"干净的结果,不要错误噪音",就把 stderr 丢掉:cmd 2>/dev/null。
程序结束时会留下一个 0–255 的整数,叫退出码。约定是:
0 —— 成功0 —— 失败,不同的值代表不同的失败原因(如 grep 没匹配到返回 1,命令不存在返回 127)退出码不会显示在屏幕上,要用 $? 取:
grep "ERROR" app.log
echo $? # 0 = 找到了,1 = 没找到,2 = 文件不存在等脚本里判断成败,必须看退出码,不能看屏幕上的文字。屏幕文字是给人看的,会变;退出码是给程序看的,是契约。&&、||、if、set -e 全都建立在退出码之上:
if mkdir /tmp/work; then
echo "目录建好了"
else
echo "建目录失败,退出码 $?"
fi重定向 > 会直接覆盖
cmd > out.txt 在打开文件时就清空它,哪怕 cmd 随后失败,out.txt 也已经被清空了。需要追加用 >>;担心覆盖重要文件,可以先 set -o noclobber,这样 > 遇到已存在文件会报错而不是覆盖。
| 误区 | 更稳妥的做法 |
|---|---|
| 用屏幕上有没有"error"字样判断脚本成败 | 判断 $? 或直接用 if cmd; then,退出码才是契约 |
| 以为 `cmd | grep` 能过滤掉报错 |
$? 取晚了,中间又跑了别的命令 | $? 只反映"上一条"命令,要判断就紧跟着取或先存进变量 |
版权归属:Shuo Liu