2つのファイルを開いてみると1文字も違わないのに、diff がすべての行が変わったと言ったり、最初の行や最後の行だけが違うと言ったりすることがあります。原因はたいてい、画面に見えない文字です。この記事では、そうした「見えない差分」の種類と解決方法をまとめます。
改行文字:CRLF、LF、CR
行を改める方法は、OS によって慣習が異なります。
| 表記 | バイト | 主に使われる場所 |
|---|---|---|
| LF | 0A (\n) | Linux、macOS、ほとんどの開発ツール |
| CRLF | 0D 0A (\r\n) | Windows のメモ帳など多くのプログラム、メール・HTTP などのインターネットプロトコル |
| CR | 0D (\r) | ごく古い Mac OS(バージョン9以前) |
一方のファイルが CRLF、もう一方が LF で保存されていると、行単位で比較したときにすべての行末にある見えない \r が1つずつ差分として検出され、全体が変わったように見えます。GNU diff では --strip-trailing-cr、git diff では --ignore-cr-at-eol でこの違いを無視できます。
ファイル末尾の改行
POSIX の慣習では、テキストファイルの各行は改行文字で終わります。そのため多くのツールは最終行の後にも改行を入れますが、入れないエディタもあります。この違いは unified diff では次のように表示されます。
@@ -1,2 +1,2 @@ host=example.com-port=8080\ No newline at end of file+port=8080
\ No newline at end of file は、すぐ上の行の末尾に改行がないという意味です。上の例では内容は同じで、変更後のファイルにだけ最後の改行が追加されました。多くのコーディング規約はファイル末尾の改行を求めており、エディタの「ファイル末尾に改行を挿入」設定で統一できます。
BOM:最初の行だけが違うとき
UTF-8 ファイルの先頭に EF BB BF の3バイトが付いていることがあります。これをバイトオーダーマーク(BOM)と呼びます。UTF-8 ではバイト順の指定は不要ですが、一部のプログラムが「このファイルは UTF-8」という目印として付けます。BOM は画面に表示されないので、最初の行だけが違うと出たら BOM を疑ってみてください。このサイトはファイルを読み込むときに先頭の BOM を取り除き、空白無視オプションをオンにすると、貼り付けたテキスト内の BOM(U+FEFF)も空白と同じように扱います。
タブとスペース、特殊な空白
- タブとスペース:タブ1つとスペース4つは、画面上では同じに見えても別の文字です。
- ノーブレークスペース(NBSP、U+00A0):ウェブページやワープロからコピーしたテキストによく紛れ込みます。
- 全角スペース(U+3000):日本語・中国語・韓国語の入力システムで入力されることがあります。
- ゼロ幅スペース(U+200B):幅が0なのでまったく見えませんが、1文字として数えられます。
これらの文字は、空白無視オプションをオンにすればほとんど取り除かれます(ただし U+200B のように空白文字に分類されない文字は、オプションをオンにしても残ることがあります)。行内比較を文字単位に切り替えると、見えない文字がどの位置にあるかを強調表示で見つけられます。
Unicode の正規化:同じ文字、違うバイト
日本語の「が」は、1文字の合成済み文字(U+304C)としても、「か」(U+304B)の後に結合用濁点(U+3099)を続けた形としても表せます。韓国語のハングル「가」も同様に、1文字(U+AC00)でも、子音 ㄱ(U+1100)と母音 ㅏ(U+1161)の組み合わせでも表せます。Unicode は前者を NFC、後者を NFD の正規化形式と呼びます(UAX #15)。2つのテキストは画面上ではまったく同じに見えますが、比較すると別の文字です。macOS で作ったファイル名が、ほかの OS で濁点が分かれた形になって見える現象が代表的な例です。比較の前に一方を NFC に変換して揃えるのが解決策です。
git で改行を統一する
複数の OS で共同作業するリポジトリなら、git の設定で改行を統一できます。
core.autocrlf:trueにすると、チェックアウト時に LF を CRLF に、コミット時に CRLF を LF に変換します(主に Windows)。inputはコミット時にだけ CRLF を LF に変換します。.gitattributes:リポジトリ単位でルールを決めるので、人によって設定が違っても結果は同じになります。
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
text=auto は、git がテキストファイルと判断したファイルの改行をリポジトリ内で LF に正規化し、eol=lf・eol=crlf は作業フォルダに取り出すときの改行を指定します。
このツールで試す
テキスト比較ツールの改行コードを正規化は既定でオンになっており、CRLF・CR・LF をすべて同じ改行とみなし、ファイル末尾の改行の有無も無視します。このオプションをオフにすると、行末の CR は ␍ で、最終改行のない行は ∅ で表示され、見えなかった差分を確認できます。空白関連のオプションは 空白無視オプションに、結果の記号の意味は unified diff の読み方にまとめています。