Shell 脚本在 CI 里行为不一致时,先开 bash -x

13 次浏览2 条回复

本地能跑、CI 却失败时,可以先让 Bash 打印实际执行过的命令,不用到处加 echo:

bash -x ./build.sh 2>trace.log

想把跟踪单独写到文件,并带上脚本名和行号,可以这样:

exec 7>trace.log
export BASH_XTRACEFD=7
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x ./build.sh
exec 7>&-

环境:Bash 4+,Linux 或 macOS。注意跟踪会把展开后的参数写出来,命令行里有令牌、密码或个人路径时别直接上传 CI 日志。

还有个容易漏的点:CI 里如果写成 sh build.sh,那就算脚本首行是 Bash 也不会按 Bash 跑,本地和 CI 实际用的 shell 可能不同。

我一般会在脚本开头明确:

#!/usr/bin/env bash
set -Eeuo pipefail

然后用 ./build.sh 或 bash ./build.sh 调起。排查时顺手打印 command -v bash 和 bash --version,先确认两边环境没偏。pipefail 也能抓到管道前段失败但最后一段成功的情况。环境:Bash 4+。

隔壁空白Lv1#1

还有个容易漏的点:CI 里如果写成 sh build.sh,那就算脚本首行是 Bash 也不会按 Bash 跑,本地和 CI 实际用的 shell 可能不同。

我一般会在脚本开头明确:

#!/usr/bin/env bash
set -Eeuo pipefail

然后用 ./build.sh 或 bash ./build.sh 调起。排查时顺手打印 command -v bash 和 bash --version,先确认两边环境没偏。pipefail 也能抓到管道前段失败但最后一段成功的情况。环境:Bash 4+。

这个补充很关键。再提醒一个容易踩坑的点:如果是在脚本内部 set -x,不要在 xtrace 还开着时直接关掉 BASH_XTRACEFD 指向的 fd,否则后面的 trace 可能回到 stderr,和普通日志混在一起。收尾时先 set +x,再 unset BASH_XTRACEFD。另外 trace 是展开后的命令,CI 的 secret masking 规则未必覆盖自定义变量,日志上传前最好确认脱敏确实生效。