CRLF と LF CIを壊すラインエンドのバグ
あなたのシェルスクリプトはローカルで動作するが、CI環境では「bad interpreter: /bin/bash^M」とエラーが発生する。これはCRLFの行終端による問題である。その原因、検出方法、そして永続的に解決するための.gitattributesの使用方法を学ぼう。
あなたのシェルスクリプトはローカルのコンピュータでうまく動きます。GitHubにプッシュすると、CIが暗黙的なエラーを表示します。 /bin/bash^M: bad interpreter あなたはソフトウェア開発における最も見えないバグに襲われました:誤ったラインエンドです。
このガイドでは、CRLFとLFが実際に何であるか、それらを混ぜることでスクリプトや設定ファイルが静かに腐食する理由、そしてラインエンドのバグがパイプラインに到達するのを防ぐための正確なステップを説明します。
CRLFとLFとは何か?
すべてのテキストファイルには、行の終わりを示す方法が必要です。これは、物理的なテレタイプ機の時代から継承された2つの規則があります:
- LF(ラインフィード) — 1つの
\n文字(バイト0x0A)。Linux、macOS、およびすべてのUnix系システムで使用されています。 - CRLF(カーソルリターン + ラインフィード) — 2つの文字、
\r\n(バイト0x0D 0x0A)。WindowsおよびMS-DOSで使用されています。
名前はタイプライターの仕組みから来ています。カーソルリターンは印刷ヘッドをラインの開始位置に戻し、ラインフィードは紙を1行進めていました。Windowsは両方を保持し、Unixは冗長なカーソルリターンを削除しました。 カーソルリターン は、 ラインフィード は、紙を1行進めるもので、Windowsは両方を保持し、Unixは冗長なカーソルリターンを削除しました。
CRLFがCIパイプラインを壊す理由
Linux(ほぼすべてのCIランナーが実行される環境)では、 \r は空白文字ではありません — それは文字としての実体です。シェルスクリプトがCRLFエンドで保存された場合、各行の終わりに \r が含まれます。カーネルはシェルの最初の行を #!/bin/bash\r と見なし、正確に名前が bash\rであるバイナリを探しています。そのバイナリは存在しません。
結果として発生するエラーは次のようになります:
: bad interpreter: /bin/bash^M: No such file or directory
の ^M は、ターミナルがカーソルリターン文字を表示する方法です。ほとんどのテキストエディタでは見えないため、このバグは非常に混乱を引き起こします。
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 diff — すべての行が変更されているように見え、実際の変更をノイズの中に埋めてしまいます。
ラインエンドの問題を検出する方法
ほとんどのエディタはデフォルトでラインエンドを隠します。信頼できるチェック方法は次の通りです:
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では、 File → Line Separators で現在のファイルを変更、または Editor → Code Style → Line separator.
でデフォルトを設定できます。
正しい解決策:.gitattributesファイル .gitattributes 一時的な解決策は拡張できません。正しい解決策は、Gitがどのラインエンドを強制するかを明確に示す
# .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 add --renormalize .
git commit -m "chore: normalize line endings via .gitattributes"
Gitのautocrlf設定 — そしてなぜそれがよくないことが多いか
Gitは、ラインエンドを自動的に変換するための core.autocrlf 設定を持っています:
core.autocrlf=true— チェックアウト時にLFをCRLFに変換(Windows)、コミット時にCRLFをLFに変換。Windowsユーザー向けに意図されています。core.autocrlf=input— コミット時にCRLFをLFに変換し、チェックアウト時には何もしません。Mac/Linuxユーザー向けに安全です。core.autocrlf=false— Gitは何もしません。エディタが保存した内容がそのままコミットされます。
問題: core.autocrlf は ローカル 設定で保存され、チーム内のすべての開発者が異なる値を持っているため、異なるマシンからコミットされたファイルは異なるラインエンドを生成します。これにより、diffに常にノイズが発生し、誰が最後にファイルを編集したかによってCIの失敗が間歇的に発生します。 ~/.gitconfig. チーム内のすべての開発者は異なる価値を持ち、異なるマシンからのコミットが異なる行終端を生成します。これにより、差分に常にノイズが生じ、ファイルを最後に編集した人が異なる場合にCIの失敗が間歇的に発生します。
ルールとして: をリポジトリにラインエンドポリシーとして設定してください。そして .gitattributes を各開発者が持つ値に設定して、 core.autocrlf がそれを上書きします。 .gitattributes CIのチェックを追加する
.gitattributesが存在する場合でも、CIに明示的なチェックを追加することは価値があります。2行のステップでほとんどのケースをカバーできます:
このステップは、元の場所であるPRで明確に失敗するのではなく、デプロイ時に静かに失敗します。 .gitattributes CRLFとLFの迅速な参照
# 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
LF(
CRLF(
バイト\n) | 使用\r\n) | |
|---|---|---|
| Linux、macOS、Unix | 0x0A | 0x0D 0x0A |
| Windows、MS-DOS | シェルスクリプトに安全 | いいえ — シェルの最初の行を破壊 |
| Dockerfileに安全 | はい | .envファイルに安全 |
| いいえ — 値に追加された\rを含む | はい | いいえ |
| Gitの推奨 | はい | リポジトリ内でLFに正常化 |
| .bat/.cmd/.ps1ファイルにのみ | CRLFとLF:CIを壊すラインエンドのバグ2 | CRLFとLF:CIを壊すラインエンドのバグ1 |
あなたも好きかもしれません
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
