DevToolBox

改行コード(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も考慮する

参考

この記事で紹介したツール

テキストの改行コードをLF・CRLF・CRの間で変換します。

正規表現パターンをテストし、一致結果を確認します。

おすすめ

2つのテキストを比較し、行単位の差分を表示します。

おすすめ