改行コード(LF・CRLF・CR)の違いと、Gitやシェルスクリプトで起きるトラブルの直し方
公開日:
WindowsとmacOS・Linuxを行き来して開発していると、「見た目は同じなのにGitの差分が全行に出る」「シェルスクリプトが動かない」といったトラブルに出会うことがあります。原因の多くは改行コードの違いです。この記事では、改行コードの種類と、実際によく起きるトラブルの見分け方・直し方をまとめます。
改行コードは3種類ある
テキストファイルの「改行」は、目に見えない制御文字で表されています。使われる文字はOSや歴史的な経緯によって異なり、主に次の3種類があります。
| 名前 | 文字 | バイト(16進数) | 主に使われる環境 |
|---|---|---|---|
| LF | \n(Line Feed) |
0A |
Linux、macOS(OS X以降)、多くのプログラミング言語・Web |
| CRLF | \r\n(Carriage Return + Line Feed) |
0D 0A |
Windows(メモ帳の既定、バッチファイルなど) |
| CR | \r |
0D |
Mac OS 9以前(現在はほとんど見かけない) |
CR(復帰)とLF(改行)は、タイプライターやプリンターで「印字位置を行頭に戻す」「用紙を1行送る」という2つの動作を表していた名残です。WindowsはこれをそのままCRLFの2文字で、Unix系はLFの1文字で表す形を採用したため、今でも違いが残っています。
エディタ上ではどれも同じ「改行」に見えるため、違いに気づきにくいのが厄介なところです。
よくあるトラブルと見分け方
シェルスクリプトが「bad interpreter」で動かない
Windowsで作成・編集したシェルスクリプトを、LinuxやWSL、Dockerコンテナ内で実行すると、次のようなエラーになることがあります。
/bin/bash^M: bad interpreter: No such file or directory
^MはCR(\r)を表しています。1行目の#!/bin/bashの末尾にCRが残っているため、「/bin/bash+CR」という名前のプログラムを探して失敗しているのです。bash スクリプト名のように直接実行した場合も、各行の末尾にCRが残るため、$'\r': command not foundなどのエラーや、出力の末尾に余計な文字が付く原因になります。
Gitの差分がファイル全体に出る
1行しか変更していないのに、git diffでファイルの全行が変更扱いになる場合は、改行コードがLFとCRLFの間で丸ごと変わっている可能性が高いです。レビューがしにくくなるだけでなく、git blameで行の変更履歴を追えなくなるという実害もあります。
ファイルの中で改行コードが混在している
コピー&ペーストや複数人での編集によって、1つのファイルの中にLFとCRLFが混ざることもあります。CSVの読み込みや行数のカウントで、結果が合わなくなる原因になります。
改行コードを確認する方法
- エディタで確認する:VS Codeでは、画面右下のステータスバーに
LFまたはCRLFと表示され、クリックすると切り替えられます。 - コマンドで確認する:Linux・macOS・WSLでは、
fileコマンドでwith CRLF line terminatorsのように表示されます。cat -A ファイル名を使うと、CRが^Mとして見えるようになります。 - Gitで確認する:
git ls-files --eolを実行すると、リポジトリ内(i/)と作業ディレクトリ(w/)それぞれの改行コードを一覧できます。 - ブラウザで確認する:改行コード変換ツールにテキストを貼り付けると、LF・CRLF・CRがそれぞれ何件含まれているかを表示できます。
Gitでの対処:.gitattributesで統一する
Gitには、コミット時・チェックアウト時に改行コードを自動で変換する仕組みがあります。設定方法は大きく2つあります。
core.autocrlf(個人の設定)
git config core.autocrlfは、各開発者のPCごとに設定する項目です。
true:コミット時にCRLFをLFへ変換し、チェックアウト時にLFをCRLFへ変換する(Windows向け)input:コミット時にCRLFをLFへ変換するだけで、チェックアウト時は変換しない(macOS・Linux向け)false:変換しない
個人の設定に頼る方法は、設定していない人が1人いるだけで改行コードが崩れるという弱点があります。
.gitattributes(リポジトリの設定)
チームで開発する場合は、リポジトリのルートに.gitattributesを置き、ルールをリポジトリ側で管理する方法が確実です。
# テキストファイルはリポジトリ内でLFに統一する
* text=auto
# シェルスクリプトは作業ディレクトリでも必ずLFにする
*.sh text eol=lf
# Windowsのバッチファイルは作業ディレクトリでCRLFにする
*.bat text eol=crlf
*.cmd text eol=crlf
# バイナリファイルは変換しない
*.png binary
*.jpg binary
text=autoは、Gitがテキストと判定したファイルをリポジトリ内でLFとして保存する指定です。eol=lf・eol=crlfを付けると、作業ディレクトリに取り出すときの改行コードも固定できます。シェルスクリプトのようにLFでないと動かないファイルは、eol=lfを明示しておくと、Windowsでチェックアウトしても壊れません。
すでにコミット済みのファイルを揃え直す
.gitattributesを追加しただけでは、既存のファイルは変換されません。設定を追加した後、次のコマンドで改行コードを正規化してコミットします。
git add --renormalize .
git commit -m "改行コードを正規化"
この操作は多くのファイルに差分を出すため、他の作業と混ぜず、単独のコミットにしておくと履歴が追いやすくなります。
1つのファイルだけを直したいとき
リポジトリの設定を変えるほどではなく、手元のファイルだけを直したい場合は、次の方法があります。
- VS Code:ステータスバーの
CRLFをクリックしてLFを選び、保存する - コマンド:
dos2unix ファイル名(CRLF→LF)、unix2dos ファイル名(LF→CRLF)。インストールされていない環境もあります - ブラウザ:改行コード変換ツールに貼り付けて、LF・CRLF・CRのいずれかへ変換する
プログラムで改行を扱うときの注意
ファイルやユーザー入力のテキストを行ごとに処理する場合、改行コードがLFだけとは限りません。JavaScriptでtext.split("\n")とすると、CRLFのテキストでは各行の末尾に\rが残ります。
// CRLF・LF・CRのいずれにも対応して行に分割する
const lines = text.split(/\r\n|\r|\n/);
正規表現で改行を扱う場合も、\r?\nのようにCRを任意にしておくと、どちらの改行コードにも対応できます。パターンの動作は正規表現テスターで、実際のテキストを使って確認できます。変換前後のテキストの違いを確かめたいときは、テキスト比較ツールも便利です。
まとめ
- 改行コードにはLF・CRLF・CRの3種類があり、見た目では区別できない
bad interpreterや^Mが出たら、CRLFが原因であることが多い- チーム開発では
.gitattributesでルールをリポジトリに持たせるのが確実 - プログラムで行を分割するときは、CRLFとCRも考慮する