サーバーにアップロードしたスクリプトを実行したら、こう言われた。

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

あるいは、Gitで何もしていないのに全行が変更扱いになった。あるいは、ファイルを`git add`したらこんな警告が出た。

warning: LF will be replaced by CRLF in config.yml

いずれも原因は同じ、改行コードです。WindowsとLinuxで「改行」の中身が違うことから起きています。

改行に「種類」があるとは、どういうことなのか。

そして厄介なのは、この問題がエラーを出さずに静かに失敗するケースがあることです。順に見ていきます。

改行は「見えない文字」である

Enterキーを押すと行が変わりますが、あそこには何も入っていないように見えて、実際には文字が入っています。使われるのは次の2つです。

名前 由来 バイト
CR(キャリッジリターン) 復帰 0D
LF(ラインフィード) 行送り 0A

そして、この組み合わせ方がOSによって違います。「A」「B」という2行を保存したときの中身を比べると、こうなります。

LF   (Linux/macOS)  41 0A 42        ← 3バイト
CRLF (Windows)      41 0D 0A 42     ← 4バイト
CR   (旧Mac OS)     41 0D 42        ← 3バイト

見た目はどれも同じ2行ですが、ファイルの中身は別物です。1行ごとに1バイトの差が出るため、1万行のファイルなら約10KBの差になります。

なぜ2種類あるのか——タイプライターの名残

そもそもCRとLFという2つの動作は、コンピュータ以前のタイプライターや電動テレタイプに由来します。紙に印字する機械では、次の行に移るために2つの物理動作が必要でした。

  • CR(復帰):印字ヘッドを左端まで戻す
  • LF(行送り):紙を1行分だけ上に送る

どちらが欠けても正しく次の行になりません。だから制御文字も2つ必要で、機械には「CR + LF」を送っていました。Windowsが今もCRLFを使っているのは、この時代からの互換性を保ち続けているためです。

一方でUnix系は「改行は論理的にはひとつの出来事なのだから、文字も1つでいい」と考え、LFだけに統一しました。機械を動かすための都合をファイルの中身から切り離した、という設計判断です。

OSごとの違い

環境 改行コード 備考
Windows CRLF メモ帳・多くのWindowsソフト
Linux LF サーバーはほぼこれ
macOS(現在) LF Unix系のため
Mac OS 9以前 CR 現在はほぼ使われない

実務で問題になるのは、ほぼWindowsで作ったファイルをLinuxサーバーに置く場面です。開発は手元のWindows、本番はLinuxという構成が一般的なため、この境界を毎日またぐことになります。

POSIXは「改行は1文字」と定めている

ではどちらが正しいのか。Unix系OSの仕様を定めるPOSIXでは、「行」がこう定義されています。

行(Line):0個以上の改行以外の文字と、それに続く終端の改行文字からなる並び

ここで注目したいのは「終端の(terminating)」という語です。改行は行と行の「区切り」ではなく、行の「終わりを示す印」として定義されています。つまりPOSIXの世界では、最終行にも改行が必要ということになります。

改行で終わっていない末尾については、別の用語が用意されています。

不完全な行(Incomplete Line):ファイル末尾にある、1文字以上の改行以外の文字の並び

Gitの差分表示で末尾に出てくる\ No newline at end of fileという表示は、まさにこれを指しています。エラーではなく「この行は不完全ですよ」という注意書きです。

そしてPOSIXの解説文書は、複数文字を使う方式について踏み込んだ表現をしています。

ここでいう改行は汎用的な行区切りではなく単一の文字である。行末に複数の文字を使うシステムで作られたファイルは、何らかの変換なしには移植性がない

Linuxサーバーの立場からすると、CRLFのファイルは「変換しないと正しく扱えないもの」という位置づけになるわけです。

ただしCRLFが「間違い」なのではない

とはいえ、CRLFが一方的に悪いわけではありません。インターネット上のプロトコルはむしろCRLFを要求します。

電子メールの仕様(RFC 5322)を見ると、ヘッダー行はCRLFで終端すると明記されており、CRLFは「復帰(ASCII 13)の直後に行送り(ASCII 10)が続くもの」と定義されています。HTTPでも同様にCRLFが使われます。

つまりファイルの世界ではLF、通信の世界ではCRLFが標準です。どちらが正しいかではなく、場所によって正解が違うと理解するのが正確です。

【要注意】エラーが出ずに静かに失敗するケース

ここが実務で最も危険な部分です。改行コードの不一致は、必ずしもエラーとして現れません。

WindowsのExcelで作ったCSVをLinux上のプログラムで読む場面を考えます。プログラムがLFだけを目印に行を区切ると、各行の末尾にCRが1文字残ります。

元データ    東京,100
バイト列    E6 9D B1 E4 BA AC 2C 31 30 30 0D 0A
                                          ↑CRが残る
最後のセル  "100\r"  ← 見た目は「100」

問題は、この値が状況によって通ってしまうことです。

処理 結果
数値に変換する 成功する(100として扱われる)
文字列として「100」と比較 不一致
商品コードでデータを突合 不一致(該当なしになる)
文字数を数える 4文字(見た目は3文字)

数値項目は問題なく通るのに、商品コードや会員IDのような文字列のキーだけが一致しない。しかもエラーは発生せず、「該当データなし」という結果が静かに返ってきます。

金額は合っているのに、なぜか一部のデータだけ取り込まれない——この症状は改行コードを疑う価値があります。

見た目では判別できないため、原因究明に時間がかかりがちなトラブルです。CSVの文字化けと同様、「目に見えない情報がファイルに含まれている」ことが根っこにあります。

スクリプトが動かなくなる仕組み

冒頭のbad interpreterエラーも同じ原理です。シェルスクリプトの1行目には、実行に使うプログラムを書きます。

正常(LF)   23 21 2F 62 69 6E 2F 62 61 73 68 0A
CRLFの場合   23 21 2F 62 69 6E 2F 62 61 73 68 0D 0A
                                            ↑余分

LinuxはLFまでを1行目として読むため、実行するプログラムの名前を/bin/bashではなく/bin/bash+CRだと解釈します。そんな名前のプログラムは存在しないので「見つかりません」となる。エラーメッセージに現れる^Mは、このCRの表示形式です。

^Mという表示を見たら改行コードが原因、と覚えておくと診断が早くなります。

Gitでの扱い方

Gitはこの問題を織り込み済みで、変換する仕組みを持っています。Gitの公式ドキュメントによれば、テキストと判定されたファイルは次のように扱われます。

ファイルがインデックスに追加されるとき、改行はLFに正規化される。逆にインデックスから作業ディレクトリにコピーされるとき、設定やプラットフォームに応じてLFからCRLFに変換されることがある

つまりリポジトリの中身は常にLFで、手元での見え方だけを環境に合わせる設計です。この前提を知らないまま設定を変えると、差分が全行変更になる事故が起きます。

設定の選び方

設定 動作 向いている環境
core.autocrlf=true 取得時CRLF・保存時LF Windows
core.autocrlf=input 保存時のみLFに変換 Linux/macOS
core.autocrlf=false 変換しない 後述の方法を使う場合

ただしcore.autocrlfは各自のPCの設定なので、メンバーごとにバラバラになりがちです。チーム開発では、リポジトリに.gitattributesファイルを置く方法が確実です。

# 基本はGitに自動判定させる
* text=auto

# シェルスクリプトは必ずLF(Linuxで実行するため)
*.sh text eol=lf

# バッチファイルは必ずCRLF
*.bat text eol=crlf

この方法なら設定がリポジトリに含まれるため、全員に同じルールが適用されます。個人設定に依存しない点が利点です。

確認と変換の方法

改行コードは見えないため、確認には専用の手段が必要です。

  • エディタで確認:VS Codeなら画面右下に「LF」「CRLF」と表示され、クリックで変更できます。サクラエディタやメモ帳(Windows 10以降)でも確認できます
  • Linux上で確認:file ファイル名を実行すると、CRLFの場合「with CRLF line terminators」と表示されます
  • Linux上で変換:dos2unix ファイル名でLFに変換できます

サーバー上で「動くはずのスクリプトが動かない」ときは、まずfileコマンドで確認するのが手早い方法です。

なお、WindowsとLinuxの境界で食い違うのは改行コードだけではありません。ファイル名の大文字小文字の扱いや使える文字にも差があり、同じように「手元では動くのにサーバーでは動かない」原因になります(ファイル名のルールの違い)。

よくある質問

Q. 改行コードが違うとファイルは壊れますか?

壊れません。データは無傷で、読み取り側の解釈が変わるだけです。変換すれば元通り扱えます。

Q. どちらに統一すべきですか?

サーバーで動かすファイル(シェルスクリプト、設定ファイル、YAMLなど)はLFが安全です。Windows専用のバッチファイルはCRLFが必要です。用途で分けるのが実務的です。

Q. メモ帳で開くと1行に見えるのはなぜですか?

古いWindowsのメモ帳はCRLFしか改行と認識しなかったためです。Windows 10以降のメモ帳はLFにも対応しており、この症状は起きにくくなっています。

Q. なぜ差分が全行変更になるのですか?

改行コードを一括変換すると、Gitからは全行のバイト列が変わったように見えるためです。内容が同じでも全行が変更扱いになります。.gitattributesを最初に決めておくと防げます。

Q. 「^M」とは何ですか?

CR(0D)を画面上で表示するときの表記です。この文字が見えたら、CRLFのファイルがLFの環境で読まれていると判断できます。

参考にした主な調査・資料

まとめ

改行コードの問題は、見えない文字が引き起こすトラブルの典型です。

  • 改行は文字であり、LF(0A)とCRLF(0D 0A)で中身が違う
  • 2種類あるのはタイプライターの物理動作の名残
  • POSIXは改行を単一の文字と定義し、複数文字方式は移植性がないとする
  • ただしメールやHTTPはCRLFを要求する。場所によって正解が違う
  • 最も危険なのはエラーが出ない失敗。数値は通るのに文字列の突合だけ一致しない
  • ^Mが見えたら改行コードを疑う
  • チームでは.gitattributesでルールを固定する

「文字化けはしていないのに、一部のデータだけ処理されない」——そんなときに改行コードを思い出せるかどうかで、原因究明の速さが変わります。