ファイル末尾の改行を自在にコントロールする

テキストファイルとは、いくつかの行の集合である。そして、行とは改行コードで区切られるまでの、一連のテキストデータの連続である。
ところで、すべての行の末尾は改行コードで終わるかと言えば、そうとも限らない。改行コードが1つもないテキストファイルも作れるし、最後の行だけ改行コードなしにすることもできる。POSIX基準では、すべての行は改行コードで終わると定義されているようだが、現実のテキストファイルは必ずしもそうなっていないのだ。
ちゃんと改行コードで終わっているか、終わっていないかの違いによって、処理環境によっては思わぬ不具合を誘発してしまうことがある。どちらかに統一する技を覚えておきたい。

改行コードで終わらない場合の不具合

  • readコマンドは、改行コードで終わらない行を読み取ってくれない...。
$ echo -ne 'ABC\nDEF\n' | while read LINE; do echo $LINE; done
ABC
DEF
$ echo -ne 'ABC\nDEF' | while read LINE; do echo $LINE; done
ABC
  • 一方、PerlRubyなら、改行コードで終わらない行もちゃんと読み取ってくれる。
$ echo -ne 'ABC\nDEF' | perl -pe ''
ABC
DEF#末尾の改行コードなし
$ echo -ne 'ABC\nDEF' | ruby -pe ''
ABC
DEF#末尾の改行コードなし
  • awkでもちゃんと読み取ってくれるが、最後の行末には余分な改行コードが付加されてしまう。
$ echo -ne 'ABC\nEFG' | awk '{print}'
ABC
EFG

改行コードで終わる場合の不具合

  • テキストファイル末尾が必ず改行コードで終わるのがPOSIX標準なのだが、場合によっては末尾の改行コードが悪さをしてしまうことも考えられる。
  • 例えば、改行コード区切りのデータを、カンマ区切りに変換しようとtrコマンドを使ってみると...
$ echo -ne 'ABC\nDEF\n' | tr '\n' ','
ABC,DEF,
  • ファイル末尾の改行コードもカンマに変換されてしまい、無駄な空データが1つ増えてしまうかも。

ファイル末尾の改行コードをコントロールする

必ず改行コードで終了する
  • awkのprintを活かせば、そんなことも簡単にできる。
$ echo -ne 'ABC\nDEF' | awk '1'
ABC
DEF
$ echo -ne 'ABC\nDEF\n' | awk '1'
ABC
DEF
  • 但し、無駄な改行コードも忠実に再現される。
$ echo -ne 'ABC\nDEF\n\n\n' | awk '1'
ABC
DEF


改行コードを削除する
  • Rubyのchompを使って、ファイル末尾の改行コードを削除してみた。
    • -0777オプションは、行区切りに存在しない文字コード(8進数で\777=16進数で\xff)を指定している。
    • 存在しない文字コードが行区切りなので、ファイル全体を一括で読み込むことになるのだ。
    • でも、巨大過ぎるテキストファイルをちゃんと処理できるか試していない...。
    • Rubychompは、末尾の改行コードを削除する。
    • 引数に""を指定することで、CR、LF、CR LFの連続する改行コードをすべて削除してくれるのだ。
    • そして、Rubyのprintは余分な改行を付加せず、そのまま出力してくれる。
$ echo -ne 'ABC\nDEF\n' | ruby -0777 -ne 'print $_.chomp("")'
ABC
DEF#末尾の改行コードなし
$ echo -ne 'ABC\nDEF\n\n' | ruby -0777 -ne 'print $_.chomp("")'
ABC
DEF#末尾の改行コードなし
$ echo -ne 'ABC\nDEF\n\n\n' | ruby -0777 -ne 'print $_.chomp("")'
ABC
DEF#末尾の改行コードなし
余分な改行コードは削除して、必ず改行コードで終了する
  • Rubyのprintをputsに変更することで、出力の末尾が必ず改行コードになるのだ。
$ echo -ne 'ABC\nDEF\n' | ruby -0777 -ne 'puts $_.chomp("")'
ABC
DEF
$ echo -ne 'ABC\nDEF\n\n' | ruby -0777 -ne 'puts $_.chomp("")'
ABC
DEF
$ echo -ne 'ABC\nDEF\n\n\n' | ruby -0777 -ne 'puts $_.chomp("")'
ABC
DEF