改行コードで壊れるものを一通り潰す — CRLF/LFとGitの.gitattributes

bad interpreter でスクリプトが動かない、1文字も直していないのにGitの差分が全ファイル全行になる、設定は合っているのにアプリが値を受け取れない。これらは別々の不具合に見えて、原因が同じことがある。改行コードだ。CRLFとLFの違いと、実際に壊れる場面、確認・変換・再発防止までを一通りまとめる。

CRとLFはそれぞれ別の動作を指していた

改行を表すバイトは2種類ある。

  • CR(Carriage Return, \r, 0x0D)— 印字位置を行頭に戻す
  • LF(Line Feed, \n, 0x0A)— 用紙を1行送る

機械式の端末では紙送りと行頭復帰の両方が必要だったので、2バイトを続けて送っていた。それが CRLF としてそのまま残ったのがWindows系で、Unix系は LF だけで足りるとした。どちらが正しいという話ではなく、歴史的な分岐である。

表記 バイト列 主な環境
LF 0A Linux, macOS, 多くの言語処理系
CRLF 0D 0A Windows, HTTPヘッダ, 多くのネットワークプロトコル
CR 0D Mac OS 9 以前(現在はほぼ見ない)

見た目はどれも「改行」なので、エディタで開いても違いは分からない。バイト列が違うことが問題になるのは、機械がその行を解釈するときだけだ。

実際に壊れる3つの場面

シェルスクリプトが実行できない

もっとも遭遇しやすい。

1
2
$ ./deploy.sh
bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory

1行目のシバンが #!/bin/bash ではなく #!/bin/bash\r になっているため、/bin/bash\r という名前の実行ファイルを探しに行って失敗している。エラーメッセージに出ている ^M が CR の表示で、これが手がかりになる。スクリプトの中身に問題は無い。

Gitの差分が全行になる

内容を1文字も変えていないのに、git diff が全行を変更として出すことがある。エディタが保存時に改行コードを一括変換したためだ。差分は行単位で比較するので、全行の末尾が変われば全行が変更扱いになる。レビューが成立しなくなるので、コミット前に気づく必要がある。

設定値に見えない文字が混ざる

.env のようなファイルをCRLFで保存すると、各行の値の末尾にCRが付く。

1
PORT=8080\r

これを文字列として比較すると 8080 と一致しない。数値としてパースすれば通ることもあるので、「ある環境変数だけ効かない」という形で出てくる。エディタで見ても何も見えないぶん、原因にたどり着くまでが長い。

いま何になっているかを確認する

まず file で判定できる。

1
2
$ file deploy.sh
deploy.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators

with CRLF line terminators と出れば CRLF。何も出なければ LF である。バイト列を直接見るなら od を使う。

1
2
$ head -1 deploy.sh | od -c | head -2
0000000 # ! / b i n / b a s h \r \n

\r \n が並んでいるのが確認できる。特定のファイルではなくリポジトリ全体を調べたいなら、Gitに聞くのが速い。

1
2
$ git ls-files --eol | grep -v 'i/lf'
i/crlf w/crlf attr/ scripts/deploy.sh

i/ がリポジトリ内(index)の改行コード、w/ が作業ツリーの改行コードを表す。i/crlf のものがコミット済みの内容としてCRLFになっているファイルだ。

変換する

1ファイルなら sed で足りる。

1
$ sed -i 's/\r$//' deploy.sh

macOS の BSD sed は -i に引数が要るので sed -i '' 's/\r$//' deploy.sh になる。dos2unix が入っていればそちらのほうが確実だ。

1
$ dos2unix deploy.sh

まとめて変換するときは、対象を絞ってから実行する。バイナリファイルに sed をかけると壊れる。

1
$ git ls-files --eol | awk '$1=="i/crlf"{print $4}' | xargs -r dos2unix

.gitattributes で再発を防ぐ

一度直しても、CRLFで保存するエディタを使っている人が触れば戻る。リポジトリ側で正規化を宣言しておくのが確実だ。

1
2
3
4
5
# .gitattributes
* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.png binary

text=auto はGitがテキストと判定したファイルを対象にする指定、eol=lf はコミットされる内容をLFに正規化する指定である。Windows専用のバッチファイルはCRLFでないと動かないので、明示的に例外にしておく。バイナリは binary を付けて正規化の対象から外す。

core.autocrlf という設定もあるが、これは各利用者のローカル設定なので、チームで揃えたいなら .gitattributes を使う。両方ある場合は .gitattributes が優先される。

既存のリポジトリに後から入れると、一度は全ファイルが変更として出る。正規化だけを行うコミットとして分けておくと、後から履歴を追うときに「この巨大な差分は改行の正規化だ」とすぐ分かる。

1
2
$ git add --renormalize .
$ git commit -m "chore: 改行コードを LF に正規化する"

--renormalize は、.gitattributes の指定に従ってインデックスを作り直す。ファイルを開いて保存し直す必要はない。

エディタ側も揃えておく

.editorconfig を置いておくと、対応しているエディタが保存時にLFで書いてくれる。Gitの正規化は「コミットされる内容」を揃えるものなので、作業ツリー側はこちらで揃えるほうが差分が出にくい。

1
2
3
4
5
6
7
8
9
# .editorconfig
root = true

[*]
end_of_line = lf
insert_final_newline = true

[*.{bat,cmd}]
end_of_line = crlf

.gitattributes.editorconfig は役割が違う。前者はGitに対する指示、後者はエディタに対する指示だ。両方置いて、同じ方針を書いておくのが分かりやすい。

まとめ

改行コードの問題は、症状がばらばらに見えるわりに原因と対処が1つにまとまっている。filegit ls-files --eol で現状を見て、dos2unix で直し、.gitattributes で再発を止める。この3手でだいたい片が付く。厄介なのは見えないことだけで、見えるようにしてしまえば難しい話ではない。


改行コードで壊れるものを一通り潰す — CRLF/LFとGitの.gitattributes
https://blog.hashito.biz/2026/08/03/git-line-endings-crlf-lf-gitattributes/
著者
hashito
作成日
2026年8月3日
著作権