改行コードの違いを体感してみる

テキストを入力して、保存して、再び画面に入力したままを表示する。これはコンピュータを操作する上で、最も基本的な欲求である。出来て当然のことなのだけど、稀に出来なくて思い悩むことがある。
最近のGUI環境は気が利いているので、ほとんどの場合、良きに計らい正しく表示してくれる。しかし、コマンドの世界では、文字コードにまつわるすべての設定を自分でコントロールする必要がある。すると、とたんにこの最も基本的な欲求を満たせなくなることが多い。(自分のこと)
なぜ文字化けしてしまうのか?なぜ1行しか表示されないのか?なぜgrepで検索されないのか?なぜ1行ずつループ処理してくれないのか?文字コードにまつわる疑問は多い...。基本的なことを理解していれば、思い悩む必要はないのに、毎回無駄に悩んで、時間を浪費している気がする。
まずは文字コードの違いから、ちゃんと調べ直してみた。

実験環境

体感する道具

  • Xcodeをインストール済みであること。
  • Homebrewをインストール済みであること。
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  • nkfコマンドをインストール済みであること。
$ brew install nkf

最終行しか表示されない現象

  • 例えば、Finderでアプリケーションフォルダを開いて、
  • プレビュー.app、マップ.app、メール.appを選択してコピー、
  • テキストエディットを開いて標準テキストの新規書類にペースト、
プレビュー.app
マップ.app
メール.app
  • ファイル名:Finder_cp.txtとして保存してみる。
  • catコマンドで確認すると...
$ cat $HOME/Documents/Finder_cp.txt
メール.app
  • 最終行のメール.appしか表示されない。
  • プレビュー.appとマップ.appはどこへ行った?

すべての行が表示される場合

  • 今度は、テキストエディットを開いて標準テキストの新規書類に、まったく同じ内容を手入力してみる。
プレビュー.app
マップ.app
メール.app
  • ファイル名:Finder_input.txtとして保存してみた。
  • catコマンドで確認すると...
$ cat $HOME/Documents/Finder_input.txt
プレビュー.app
マップ.app
メール.app
  • ちゃんと3行すべてが表示された。

ダンプ表示で違いを知る

  • この違いを知るには、odコマンドでダンプ(16進数コード)表示してみると、よく分かる。
$ od -tx1c $HOME/Documents/Finder_cp.txt
0000000    e3  83  97  e3  83  ac  e3  83  93  e3  83  a5  e3  83  bc  2e
          プ  **  **  レ  **  **  ビ  **  **  ュ  **  **  ー  **  **   .
0000020    61  70  70  0d  e3  83  9e  e3  83  83  e3  83  97  2e  61  70
           a   p   p  \r  マ  **  **  ッ  **  **  プ  **  **   .   a   p
0000040    70  0d  e3  83  a1  e3  83  bc  e3  83  ab  2e  61  70  70    
           p  \r  メ  **  **  ー  **  **  ル  **  **   .   a   p   p    
0000057

$ od -tx1c $HOME/Documents/Finder_input.txt
0000000    e3  83  97  e3  83  ac  e3  83  93  e3  83  a5  e3  83  bc  2e
          プ  **  **  レ  **  **  ビ  **  **  ュ  **  **  ー  **  **   .
0000020    61  70  70  0a  e3  83  9e  e3  83  83  e3  83  97  2e  61  70
           a   p   p  \n  マ  **  **  ッ  **  **  プ  **  **   .   a   p
0000040    70  0a  e3  83  a1  e3  83  bc  e3  83  ab  2e  61  70  70    
           p  \n  メ  **  **  ー  **  **  ル  **  **   .   a   p   p    
0000057
  • オレンジ色の太字がその違い。
  • ちょうど改行する部分である。
  • \rは、CR(キャリッジリターン)の意味。16進数コードで0dになっている。
    • 印字ヘッド(キャリッジ)を行頭に戻す(リターン)、という意味。
  • \nは、LF(ラインフィード*1)の意味。16進数コードで0aになっている。
    • 1行分(ライン)用紙を送る・すすめる(フィード)、という意味。

改行コードの違い

  • 通常、UNIX環境はLFを改行コードと解釈する。
    • OSXUNIX環境なので、LFを改行コードと認識するのだ。
  • 一方、OS9までのMacOSは、CRを改行コードと解釈していた。
  • ちなみに、Windows環境は、CR LFというコードの並びを改行コードと解釈する。
改行コード 代表的な環境
LF(0x0a) UNIX
CR(0x0d) MacOS
CRLF(0x0d0a) Windows
  • CRを見つけたUNIX環境は、文字を出力する位置を行頭に戻す。
  • ところが、改行はしないので、次の文字は同じ行に上書きされることになる。
  • そのような動作が繰り返され、最終行の「メール.app」のみが見えていたのだ。

正しく改行する

  • テキストエディット.appは、どのような改行コードも良きに計らい解釈してくれる。とても親切である。
  • 一方、コマンドの世界では、基本的にはOS環境に依存した改行コードしか、改行と認めてくれない。
  • そのため、正しく改行するためには、改行コードを変換する必要があるのだ。
$ cat $HOME/Documents/Finder_cp.txt | tr '\r' '\n'
プレビュー.app
マップ.app
メール.app
  • これで正しく表示された、と喜んではいけない。
  • もしWindows環境のファイルだったら、余分な空行が入ることが目に見えている。
プレビュー.app

マップ.app

メール.app
  • CR LF → LF LF と変換されてしまうので。
  • CR LFの改行コードにも対応するなら、もうちょっと工夫する必要がある。
  • まず思いつくのはsedによる置き換えだが...これはNG。
$ cat $HOME/Documents/Finder_cp.txt | sed 's/\r\n|\r/\n/g'
メール.app.app
  • 一方、perlを使えば、同じ正規表現でちゃんと変換してくれる。
$ cat $HOME/Documents/Finder_cp.txt | perl -pe 's/\r\n|\r/\n/g'
プレビュー.app
マップ.app
メール.app
  • ちなみに、rubyでも変換できる。
$ cat $HOME/Documents/Finder_cp.txt | ruby -Ku -pe 'gsub(/\r\n|\r/,"\n")'
  • いやいや、今やnkfコマンドをインストールしているのならnkfを使うべき。
$ cat $HOME/Documents/Finder_cp.txt | nkf -Lu
プレビュー.app
マップ.app
メール.app
  • nkfなら、他の改行コードもオプション指定だけで自在に変換できるのだ。
オプション 代表的な環境 改行コード
nkf -Lu UNIX環境 LF
nkf -Lm MacOS環境 CR
nkf -Lw Windows環境 CR LF


OSX環境でテキスト処理をするのなら、nkf -Luを覚えておく価値がある。

*1:またはNL(ニューライン)と呼ばれることもある。