Jenkins 构建步骤退出码非零速查:sh 失败语义、set +e 与 catchError 分级
更新时间:2026-10-24。FAQ 速查页,面向构建日志结尾躺着
script returned exit code 1、却不知道是哪条命令挂掉的同学。Jenkins 的sh步骤有一条铁律:整个脚本的退出码就是最后一条命令的退出码,而非零即把整步判为 FAILURE——但 shell 默认「出错继续跑」,真正的凶手往往死在脚本中段,日志最后一行却是后面某条无辜命令的退出码。病因收敛在六根轴上:脚本中段某条命令失败被后续命令掩盖、多行脚本没开set -e导致错误静默穿透、set +e场景下失败被彻底吞掉、退出码本身被误用(grep 无匹配返回 1)、catchError 把失败降级成 UNSTABLE 掩盖了真错误、以及 Windowsbat步骤的失败语义和 sh 不一样。顺一遍基本必中。如果是命令本身不存在而不是返回非零,转 Jenkins 命令找不到速查。
本文要回答的问题
script returned exit code 1到底是哪条命令返回的 1?- 为什么日志里明明有报错,构建却显示成功?
sh步骤里写多条命令,第一条挂了后面的还跑吗?- grep 没匹配到东西返回 1,怎么不让它拖垮构建?
- catchError 和 warnError 有什么区别,什么场景用哪个?
一、报错原文与典型现象
退出码类报错的固定位置是构建日志的 sh 步骤结尾(控制器地址以 jenkins.example.com 占位,路径以 /proj/demo 占位):
# sh 步骤失败的典型结尾
[Pipeline] sh
+ make test
make: *** [Makefile:15: test] Error 2
[Pipeline] }
ERROR: script returned exit code 2# 整步失败但看不出哪条命令挂的
+ deploy.sh
+ echo done
done
ERROR: script returned exit code 1# 被 catchError 降级后的样子
[Pipeline] catchError
ERROR: script returned exit code 1
# 构建结果 UNSTABLE 而非 FAILURE
Finished: UNSTABLE读现象抓两个信号:script returned exit code N 里的 N 是凶手留下的指纹——1 是通用错误、2 常是 shell 内建或 make 规则失败、126 是权限不够不可执行、127 是命令不存在、137 是被 OOM 杀掉、143 是被超时中断;+ 开头的行是 shell 的 xtrace 输出,最后一条 + 行之后到报错之间,就是真正失败的命令。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| exit code 1 但看不出哪条挂了 | 脚本中段失败被后续命令掩盖 | 开 xtrace 逐条对照 + 行 | 脚本首行加 set -e,或拆成多个 sh 步骤 |
| 日志有报错但构建成功 | 失败命令不在脚本末尾,退出码被覆盖 | 检查脚本最后一条命令 | 开 set -e 让任一失败立即终止并透出 |
| grep 无匹配拖垮构建 | grep 无命中返回 1 被当失败 | 单独跑该 grep 看退出码 | 追加 |
| exit code 127 | 命令不存在或 PATH 不对 | 同 agent 上手跑 which 验证 | 修 PATH 或装依赖(见命令找不到篇) |
| exit code 137 / 143 | OOM 被杀或超时被中断 | 看 dmesg 与构建超时设置 | 降内存占用或调大超时与容器内存 |
| 构建 UNSTABLE 而非 FAILURE | catchError 把失败降级了 | 查 Jenkinsfile 里 catchError 包裹点 | 确认分级是否符合预期,别拿它吞真错误 |
| Windows bat 步骤行为不同 | bat 的失败语义与 sh 不一致 | 对比同脚本在 sh 下的表现 | bat 内显式检查 errorlevel 并主动退出 |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是报错入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓分水岭:先把退出码 N 读出来——1 和 2 是脚本逻辑层面的问题,走左支用 xtrace 定位真凶;126、127、137、143 是环境与资源层面的问题,走右支转对应的专门篇目。逻辑类的主药是 set -e 与显式的容错语义,不是把失败命令后面的输出删掉装没看见。
四、逐条排查展开
sh 步骤的退出码语义:整个脚本等于最后一条命令。 这是一切困惑的根源。你在一个 sh 块里写了五行命令,第二行挂了,shell 默认继续往下跑,第五行成功返回 0——整个步骤判成功,第二行的失败被无声吞掉。反过来,第二行挂了、第五行也顺手返回了非零(比如清理动作遇到空目录),报错的锅扣在第五行头上。定位真凶靠 shell 自带的 xtrace:Jenkins 的 sh 步骤默认会打印 + 开头的执行轨迹,从日志尾部往上翻,报错行之前最后一条 + 命令就是实际返回非零的那条。如果 xtrace 被关了,脚本首行加 set -x 重新打开。
set -e 是兜底网,不开就是在裸奔。 脚本首行写 set -e(或 #!/bin/bash -e)之后,任何一条命令返回非零立即终止脚本并透出该退出码——失败发生在哪一行,构建就停在哪一行,日志与真凶同框。更进一步的标准姿势是 set -euo pipefail 三件套:-e 出错即停,-u 引用未定义变量即停(防拼错的变量名静默展开成空串),pipefail 让管道里任意一环失败都反映到整体退出码(a | b | c 里 b 挂了默认只透 c 的退出码)。老脚本改造时注意:set -e 会让原本「挂了也无所谓」的命令终止构建,需要显式豁免的用 || true 标注。
grep 返回 1 是特性不是错误。 grep 无匹配时退出码为 1,这是 POSIX 语义;但 sh "grep ERROR app.log" 在没错误日志时反而拖垮构建,极具讽刺性。同理 test/[ 判假返回 1、某些清理命令目标不存在返回非零。处置不是开 set +e 全吞,而是点对点豁免:grep ERROR app.log || true 语义明确——这条命令的失败我认了;或者用判空逻辑 if grep -q ERROR app.log; then ... fi,条件语句里的命令不受 set -e 影响,这是最干净的写法。
特殊退出码先查表再排查。 126 是「找到命令但没有执行权限」(常见于脚本文件没加执行位或挂载成 noexec);127 是「命令不存在」(PATH 问题,转 Jenkins 命令找不到速查);137 = 128 + 9,进程被 SIGKILL,九成是 OOM,查 agent 的内存与容器 limit(转 Jenkins 资源耗尽速查);143 = 128 + 15,SIGTERM,常见于构建超时被 Jenkins 中断(转 Jenkins 超时挂起速查)。退出码大于 128 的统一读法:减去 128 就是杀死进程的信号编号。
catchError 与 warnError:分级工具不是遮羞布。 两者都包裹一段步骤:catchError(stageResult: 'UNSTABLE') 在内部失败时把整构建降级成 UNSTABLE 继续跑;warnError 更进一步,失败只记警告、构建状态不变。正确用途:非关键步骤(如上传覆盖率、发送通知)失败不该拖垮主流程。错误用途:把经常失败的核心步骤包进 catchError 让构建常绿——问题没修,只是把报警器电源拔了。团队纪律:每个 catchError 必须在代码评审里说明「为什么这一步允许失败」,说不出理由的拆掉。
Windows agent 的 bat 步骤语义不同。 bat 步骤走的是 cmd.exe,没有 set -e 的概念,批处理里命令失败默认继续跑;整个 bat 块的退出码取决于最后的 errorlevel。跨平台团队在 Linux 上养成的 set -e 直觉在 bat 里会踩空。修法:bat 脚本里关键命令后显式判 if errorlevel 1 exit /b 1,或干脆把复杂逻辑挪进 PowerShell 步骤(powershell/pwsh),它的错误终止语义更接近 shell。
五、规范做法与预防
- sh 脚本三件套默认开:所有新脚本首行
set -euo pipefail;豁免用|| true点对点标注,禁整段set +e。 - 一步一 sh:长脚本拆成多个 sh 步骤,每步职责单一——哪步失败看构建页的 stage 高亮就知道,不用翻 xtrace。
- 特殊退出码进团队速查表:126/127/137/143 的含义贴在 CI 文档首页,新人照表分流,不在错误的方向上排查。
- catchError 评审制:包 catchError 的步骤必须在评审中给出「允许失败」的理由;核心构建/测试步骤禁止包裹。
- 容错用条件语句:判存性、判匹配用
if包住,让失败语义变成分支语义,而不是用|| true撒一地。 - Windows 脚本显式判 errorlevel:bat 步骤关键命令逐条判错误码,复杂逻辑优先 pwsh。
这一篇在知识体系里的位置
本篇是 Jenkins 排错速查簇的「失败语义」枢纽篇:退出码 127 命令本身不存在看 Jenkins 命令找不到速查;137 资源被杀看 Jenkins 资源耗尽速查;143 超时中断看 Jenkins 超时挂起速查;变量拼错被 set -u 拦下后的引用问题看 Jenkins 凭据与变量找不到速查。「读退出码定类 → 逻辑类开 xtrace 定位、环境资源类转专篇」这条主线对一切步骤失败通用。
一句话总结
Jenkins 构建步骤退出码非零先读 N 定类:1/2 是脚本逻辑问题,靠 xtrace 的 + 行从日志尾部往上定位真凶——sh 步骤的退出码等于最后一条命令,中段的失败要靠 set -euo pipefail 兜底透出;126 权限、127 命令不存在、137 OOM、143 超时中断各有专篇对接。grep 无匹配返回 1 这类「假失败」用 || true 或 if 条件点对点豁免,别开 set +e 全吞。catchError/warnError 是给非关键步骤的分级工具,包核心步骤等于拔报警器电源。三件套默认开、一步一 sh、退出码速查表上墙,这类报错基本绝迹。