テキストファイルの末尾は改行コードで終わっているべきか?

テキストの設定ファイルなどで、最後の行を改行コードで終わらすべきか、あるいは改行コード無しで終わらすべきかで迷ったことはありませんでしょうか?

Pythonでプログラムを作っているときに、ふと「Pythonでこれらのファイルを読み込んだときはどうなる?」か気になりましたのでどちらが良いか改めて考察してみたいと思います。

目次

結論は改行コードで終わっているべき

結論から言うと「テキストファイルは改行コードで終わっているべき」です。この結論に至るにはいくつか理由があります。その理由を1つずつみていきましょう。

行の定義

そもそもテキストファイルにおける「行とは何か?」という定義から考えてみましょう。

昔読んだ「プログラミング言語C」に「行の最後を改行コードで終わらせるのは出力するプログラムの責任」というようなことが書いてありました。これはテキストファイルの末尾が改行コードで終わることを示唆しています。

そしてwcコマンドの骨子のCプログラム例があって、このプログラムは行が改行コードで終わってないと、正しく行数をカウントできなかったことを覚えています。

当時は改行コードで終わっていない行を正しくカウントできなくていいのだろうか?と悩みました。実際のwcコマンドでも改行コードで終わっていない行は、行としてカウントしません。

しかし、よくよく調べてみるとwcコマンドは「改行数を出力する」とのドキュメントがありました。つまり、行数ではなく改行コードの数をカウントするのです。けれども私のMac環境でwcコマンドのドキュメントにを見てみると、-lオプションは「行数を出力する」と記載があります。原文は「The number of lines in each input file is written to the standard output.」です。

「改行数を出力する」と「行数を出力する」のどちらが正しいのでしょうか?

どうやら改行数をカウントすると言うのが正しそうです。ネット上には改行コードで終わっていないテキストファイルの行数をwcコマンドでは正しくカウントできないとの情報もあり、どのwcコマンドの実装も改行数をカウントしているそうです。

wcコマンドが正しくカウントできないので、行は改行コードで終わるべきと結論づけたかったのですが、振り出しに戻ってしまいました。wcコマンドの新しい知見を得られたので良しとしますが、改めて考えて見ましょう(長くなってすみません)。

次はプログラムの観点から考察して見ましょう。プログラムがテキストファイル を読むとき、低レベルではファイルの先頭から1文字ずつ文字を読んで行きます。そして改行コードを見つけたときにそれまで読んだの文字列を行として認識できます。そしてまた改行コードの次の文字から読んで行きます。

改行コードで終わるテキストファイル の場合は、最後に改行コードを読んで最終行を認識します。そして改行コードの次の文字を読もうとするとファイルの終わりに達しているのでファイル読み込み処理が終了します。

一方、改行コードで終わらないテキストファイルの場合、最後の行の文字を順に読んでいて、突然ファイルの終わりに達し処理が終了します。最後の行以外は改行コードに達して行を認識しますが、最終行だけファイルの終わりに達して行を認識することになります。これでは処理の一貫性に欠けます。

これはwcコマンドでも同じことが言えます。「行は改行コードで終わる」という定義であれば「改行数を出力する=行数を出力する」となるのでスッキリします。そもそも改行数を知りたいというニーズがどれほどあるのでしょうか?大抵は行数を知りたいのではないでしょうか。wcコマンドは「行は改行コードで終わる」と言う定義のもと、行数を数えるために改行コードをカウントしているのではないでしょうか(プログラミング言語Cのサンプルプログラムは確かそうだったと思います)。

このように一貫性があるのは改行コードで終わるテキストファイルだと思います。

テキストエディターが自動的に改行コードを挿入する

いくつかのテキストエディターは、末尾が改行コードで終わっていないファイルを作成できない、あるいは末尾が改行コードで終わっていない場合、保存時などに自動的に改行コードが挿入されます。

Mac環境で試してみましたが、viエディタは最終行でEnterを入力しなくても保存したファイルの末尾は改行コードで終わります。と言うより、viエディタの場合は最終行でEnterを入力して改行してはいけないようです。

viエディタでテキスト「line2」の後にEnterを押して改行した場合は次のような画面になります。「line2」の次の行に先頭が来ます。この行は「~」が表示されていないので、この行も作成されているのがわかります。

line1
line2
← ここにカーソルがある状態
~                                                                               
~                                                                               
~           

この状態でファイルに保存すると「line2」の後に空行ができてしまいます。

Emacsの場合、テキストの最後を改行しないで終わっていても、改行して終わっていても保存されたファイルは最後の行(line2)が改行された状態になります。viのように最後が空行になることはありません。

line1
line2← ここが最後でも
← line2の後に改行してここが最後でも大丈夫

このようにデフォルトでテキストファイルの末尾が必ず改行コードで終わるようになっているエディターの存在は、やはり行は改行コードで終わるべきという考えのもとにプログラムが作られていると推測できます。

プログラムが改行コードで終わっていることを期待している

最後は、行が改行コードで終わっていることを期待しているプログラムがあることです。wcコマンドも実際はそうかもしれないことは前述しました。

大抵のプログラムはおそらく改行コードで終わっていることを期待しつつも、最終行に改行コードがない(特殊な)場合にも対応するように書かれていると思います。

検証

検証用のテキストファイルを作成する

検証ように末尾が改行コードで終わっているテキストファイルと終わっていないテキストファイルを作成します。ここではPythonを使ってファイルを作成します。

>>> open('with_newline.txt', 'w', newline='').write('line1\nline2\n')
12
>>> open('no_newline.txt', 'w', newline='').write('line1\nline2')
11

with_newline.txtファイルはline1とline2は改行コードLFで終わっています。no_newline.txtファイルはline2の後に改行コードがありません。

ここでopen()関数の引数に「newline=”」を指定しているのは、どの環境でも「\n(LF)」をそのまま書き込むためです。newline引数を省略するとWindows環境では改行コードが「\r\n(CRLF)」に変換されて書き込まれます。

つまり「newline=”」を指定するとWindowsであろうとLinuxであろうと、どの環境でも同じ改行コードのテキストファイルが出来上がります。

newline引数の詳細な動作は以下の記事を参照ください。

作成したファイルをcatコマンドで表示すると次のようになります。

$ cat with_newline.txt 
line1
line2
$ cat no_newline.txt 
line1
line2$ 

no_newline.txtファイルではline2の後に改行コードが無いので、line2の直後にプロンプト($)が表示されています。これで検証用のファイルは作成完了です。

wcコマンドで行数を数える

それでは先ほど作成したファイルでwcコマンドで数えて見ましょう。

$ wc -l with_newline.txt 
       2 with_newline.txt
$ wc -l no_newline.txt 
       1 no_newline.txt

ご覧の通りno_newline.txtはカウントが1になっています。「wc -l」が改行の数(改行コードの数)を数えるならばこれは正しい結果です。

Pythonで行数をカウントする

Pythonで行数をカウントするプログラムを作って見ましょう。

>>> def count_line_num(filename):
...     count = 0
...     fin = open(filename, newline='')
...     for line in fin:
...         count += 1
...         print(line.replace('\n', '\\n'))
...     print(f'行数は {count} です。')
...     fin.close()
... 

count_line_num()関数はファイル名を引数にとり、そのファイルの行数を数えます。行をカウントすると同時に、その行も標準出力にプリントします。改行コード(\n)はエスケープシーケンスとして出力するため「\\n」に置換してから出力しています。

先ほど作成した2つのファイルに対して実行した結果は次の通りです。

>>> count_line_num('with_newline.txt')
line1\n
line2\n
行数は 2 です。
>>> count_line_num('no_newline.txt')
line1\n
line2
行数は 2 です。

どちらのファイルも行数は2です。

ここでは行数をカウントするだけですが、多くの場合は行のテキストが必要になるでしょう。その場合は不要な改行コードは削除することになると思います。それにはstrオブジェクトのrstrip()メソッドを使うといいでしょう。

次のプログラムは改行コード以外の行のテキストを1行に表示します。

>>> def print_line(filename):
...     fin = open(filename, newline='')
...     for line in fin:
...         print(line.rstrip('\n'))
...     fin.close()
... 
>>> print_line('with_newline.txt')
line1
line2
>>> print_line('no_newline.txt')
line1
line2

このプログラムは改行コードにCRLF(\r\n)を使うテキストファイルでは正しく動作しません。open()関数の引数として「newline=”」をしているためCRLF(\r\n)はそのまま読み込まれてしまうためです。このような事態を避けるため、一般には「newline=”」を省略し、どの改行コードも常にLF(\n)に変換さるようにするのが最善です。

しかし、最後が改行コードで終わることを期待していて、次のようにプログラムが実装されていた場合、たちまち正しく動作しなくなります。

>>> def wrong_print_line(filename):
...     fin = open(filename, newline='')
...     for line in fin:
...         print(line[:-1])
...     fin.close()
... 
>>> wrong_print_line('with_newline.txt')
line1
line2
>>> wrong_print_line('no_newline.txt')
line1
line
>>> 

wrong_print_line()関数では行の最後に1文字の改行コードがあるとして、必ず行末の1文字を削除しています。これは極端な例ですが、このように改行コードで終わらないファイルでプログラムが正しく動作しないことが実際にあります。

まとめ

繰り返しになりますが「テキストファイルの最後は改行コードで終わる」のが良いでしょう。改行コードで終わっていることで問題が出ることはまずないでしょう。少なくとも改行コードで終わっていないことよりも問題は発生しないでしょう。

テキストファイルを出力するプログラムではプログラミング言語Cが述べてたように、すべての行は改行コードで終わるようにしましょう。わざわざ一貫性を崩してまで最後の行だけ改行コードなしにすることはないと思いますが。

反対にテキストファイルを読み込むプログラムでは、最後に改行コードがないファイルを読んでも正しく動作するように実装しましょう。

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

・PM、SE、SIなどを20年以上経験
・ネットワークスペシャリスト、セキュリティスペシャリストなど複数の資格を保有

目次