不喜欢广告? 无广告 今天

CRLF 与 LF 破坏 CI 的换行符问题

发布日期

您的shell脚本在本地可以正常运行,但在CI环境中会报错'bad interpreter: /bin/bash^M'。这是CRLF换行符的问题。了解其成因、如何检测以及如何通过.gitattributes永久修复。

CRLF 与 LF:破坏 CI 的行结束符 bug 1
广告 移除?

你的 shell 脚本在笔记本电脑上运行完美。你将其推送到 GitHub,但 CI 流程却出现一个神秘的错误。 /bin/bash^M: bad interpreter 你刚刚遭遇了软件开发中最隐蔽的 bug:错误的行结束符。

本文将解释 CRLF 和 LF 实际上是什么,为什么混合使用会静默地破坏脚本和配置文件,以及如何采取确切步骤,防止行结束符错误再次进入你的流水线。

CRLF 和 LF 是什么?

每个文本文件都需要一种方式来标记行的结束。两种约定源自物理电传打字机时代:

  • LF(换行符) — 一个 \n 字符(字节 0x0A)。在 Linux、macOS 和所有基于 Unix 的系统中使用。
  • CRLF(回车符 + 换行符) — 两个字符, \r\n (字节 0x0D 0x0A)。在 Windows 和 MS-DOS 中使用。

这些名称源自打字机的工作原理。一个 回车符 将打印头移动到行的开头。一个 换行符 将纸张推进一行。Windows 保留了两者;Unix 则去除了冗余的回车符。

为什么 CRLF 会破坏 CI 流水线?

在 Linux 上(几乎所有 CI 运行器都在此环境执行), \r 不是空白字符——它是一个字面字符。当 shell 脚本以 CRLF 结尾保存时,每行末尾都会包含 \r 在换行符之前。内核将 shebang 行解释为 #!/bin/bash\r 并寻找一个名为 bash\r的二进制文件。该二进制文件并不存在。

产生的错误看起来如下:

: bad interpreter: /bin/bash^M: No such file or directory

^M 是终端显示回车字符的方式。它在大多数文本编辑器中是不可见的,这使得这个 bug 非常令人困惑。

CRLF 在其他地方静默造成破坏

  • Dockerfile — 包含 CRLF 的 RUN 指令会在每个命令中注入 \r ,从而破坏字符串比较和文件路径。
  • Python 脚本SyntaxError: unexpected character after line continuation character\ 后跟 \r\n.
  • .env 文件 — 环境变量值会附加一个尾随的 \r,因此 APP_ENV=production\r 永远不会匹配预期的 production.
  • CSV 和数据文件 — 每行逐行解析的解析器可能会在每一行的最后一个字段中包含 \r
  • SSH authorized_keys — 使用 CRLF 编码的密钥文件将被 SSH 服务端静默拒绝。
  • Git 差异 — 每一行都显示为已更改,掩盖了真正的变更。

如何检测行结束符问题?

大多数编辑器默认隐藏行结束符。以下是可靠的检测方法:

使用 cat 或 hexdump

# Show ^M characters
cat -A yourfile.sh | head -5

# Hex dump to see 0x0d (CR) characters
hexdump -C yourfile.sh | head -10

使用 file 命令

file yourfile.sh
# CRLF output: yourfile.sh: ASCII text, with CRLF line terminators
# LF output:   yourfile.sh: ASCII text

使用 grep

# Returns exit code 0 (found) if CRLF endings exist
grep -rlP "\r" . --include="*.sh" --include="*.py" --include="*.yml"

如何修复 CRLF 行结束符?

选项 1:dos2unix(最快的一次性修复)

dos2unix 移除文件中的回车符。它在所有主要 Linux 发行版中都可用:

# Fix a single file
dos2unix yourscript.sh

# Fix all shell scripts recursively
find . -name "*.sh" -exec dos2unix {} \;

# Reverse: convert LF to CRLF (unix2dos)
unix2dos yourfile.sh

选项 2:sed(无需额外工具)

# Remove carriage returns in-place
sed -i 's/\r//' yourscript.sh

# Or using tr
tr -d '\r' < input.sh > output.sh

选项 3:在 VS Code 或 JetBrains 中修复

在 VS Code 中,行结束符模式显示在状态栏(右下角)。点击它以切换当前文件的 CRLF 和 LF。要更改新文件的默认设置,请在 "files.eol": "\n" 中设置 settings.json.

在 JetBrains IDE 中,前往 文件 → 行分隔符 以更改当前文件,或在 编辑器 → 代码样式 → 行分隔符.

中设置默认值。

正确的解决方案:.gitattributes 文件 .gitattributes 一次性修复无法扩展。正确的解决方案是创建一个

# .gitattributes — commit this to the root of your repository

# Default: normalize all text files to LF in the repo
* text=auto eol=lf

# Explicitly enforce LF for scripts and configs
*.sh    text eol=lf
*.bash  text eol=lf
*.py    text eol=lf
*.rb    text eol=lf
*.yml   text eol=lf
*.yaml  text eol=lf
*.json  text eol=lf
*.env   text eol=lf
Dockerfile text eol=lf
Makefile   text eol=lf

# Windows-only files can keep CRLF
*.bat  text eol=crlf
*.cmd  text eol=crlf
*.ps1  text eol=crlf

# Binary files — never touch line endings
*.png  binary
*.jpg  binary
*.gif  binary
*.zip  binary
*.pdf  binary

文件,该文件明确告诉 Git 应该强制使用哪些行结束符,无论贡献者使用的是哪种编辑器或操作系统。

git add --renormalize .
git commit -m "chore: normalize line endings via .gitattributes"

添加此文件后,运行以下命令以一次性重新规范化整个仓库:

Git 的 autocrlf 设置——以及为何它常常使问题更严重 core.autocrlf Git 有一个

  • core.autocrlf=true 设置,旨在自动转换行结束符:
  • core.autocrlf=input — 在检出时将 LF 转换为 CRLF(Windows),在提交时将 CRLF 转换为 LF。适用于 Windows 用户。
  • core.autocrlf=false — 在提交时将 CRLF 转换为 LF,在检出时不做任何操作。更安全用于 Mac/Linux。

— Git 不做任何操作。无论编辑器保存的内容是什么,都会被提交。 core.autocrlf 问题: 是存储在 中的一个本地设置。团队中的每个开发人员都有不同的值,因此来自不同机器的提交会产生不同的行结束符。这在差异中产生持续噪音,并在不同开发者最后修改文件时导致间歇性 CI 失败。 ~/.gitconfig。团队中的每个开发人员都有不同的价值,因此来自不同机器的提交会产生不同的行末符。这会在差异对比中产生持续的噪音,并导致CI构建失败,具体取决于最后修改文件的是哪位开发人员。

经验法则: 使用 .gitattributes 在仓库中设置行结束符策略。让 core.autocrlf 保持为每个开发人员的原始值—— .gitattributes 会覆盖它。

添加 CI 防护

即使设置了 .gitattributes ,仍建议在 CI 中添加显式的检查,以捕获任何可能漏掉的文件。一个两行步骤即可覆盖大多数情况:

# In your CI workflow (GitHub Actions example)
- name: Check for CRLF line endings
  run: |
    if grep -rlP "\r" . --include="*.sh" --include="*.py" --include="*.yml" --include="Dockerfile"; then
      echo "ERROR: CRLF line endings found. Run dos2unix on the above files."
      exit 1
    fi

此步骤在源头(PR)处明确失败,而不是在部署时静默失败。

快速参考:CRLF 与 LF

LF(\n)CRLF(\r\n)
字节0x0A0x0D 0x0A
使用于Linux、macOS、UnixWindows、MS-DOS
适用于 shell 脚本是的不适用——破坏 shebang
适用于 Dockerfile是的
适用于 .env 文件是的不适用——在值中添加尾随 \r
Git 推荐在仓库中规范化为 LF仅适用于 .bat/.cmd/.ps1 文件
想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?