9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「ファイルの末尾の改行は POSIX の仕様」はガセネタ

Last updated at Posted at 2024-08-04

はじめに

$\color{#e02020}{\huge{\bf{それは単なる「POSIX標準規格書の用語集」じゃい!}}}$

ということで、まことしやかに POSIX がテキストファイルの仕様を決めているかのような噂が流れていますが(私も言ったことがあるかも?)、POSIX に「テキストファイルは末尾に改行を書かなければならない」なんて仕様はありません。また POSIX は「テキストファイルの末尾を改行にすべし」などとも言っていません。POSIX は「3. Definitions(定義)」で 「POSIX 標準規格書の中で使用している『テキストファイル』という用語の正確な意味は・・・である」と言っているだけなんです。用語の意味を定義しただけであることも、ちゃんと POSIX に書かれています。

  • ❌️ POSIXはテキストファイルの末尾に改行を書かなければならないと決めた
  • ⭕ POSIXでは末尾に改行があるものをテキストファイルと呼んでいる

POSIX の定義では「末尾に改行がないテキストファイル」をテキストファイルとは呼びません。このようなファイルをなんと呼ぶかについては POSIX の定義では単にファイルと呼ぶか、バイナリファイルと呼ぶしかないのですが、本記事では「テキストファイルもどき」と呼ぶことにします。テキストファイルもどきはテキストファイルとバイナリファイルの中間で、ある程度テキストファイルと同じように扱うことができるが POSIX で定義されたテキストファイルの条件を満たさないものとします。

POSIX コマンドの中には一見テキストファイルを読み込むように思えて、バイナリファイルやテキストファイルもどきを読み込むコマンドがあります。つまり末尾に改行があるファイルにしか対応していないわけではありません。POSIXでは「テキストファイルもどき」を「テキストファイル」とは呼びませんが、「テキストファイルもどき」の作成を禁止してはいません。バイナリファイルの作成だって禁止していないのですから当然ですよね?

テキストファイルの末尾に改行を入れる理由について「POSIX でそう決まっているから」で納得している人が多いようですが、不思議でなりません。それは「なんで男子は全員丸坊主で女子は全員おかっぱかって? 校長がそう決めたんだよ。校則でそう決まっているから校則を守れ!」といっているのと同じことです。ルールで決まっているというのは、なぜそのようなルールが有るのかの説明にはなっていません。ルールがあるから従っておくかというのは、考えることを放棄しているだけです。

この記事の内容は(POSIX)標準規格の役割を正しく理解するのは難しいという話でもあります。POSIXがテキストファイルという用語の意味をあのように定義した理由は、POSIX コマンドの仕様を正確に記述するのに都合が良いからです。人々にテキストファイルの末尾に改行を入れるように強制したかったわけではありません。自称 POSIX の代理人は「テキストファイルの規格は POSIX が定めているんだから POISX に従え」などと POSIX が言ってもいないことを言って自由を奪おうとしますが、将来の自由を妨げることがないように標準規格を策定している POSIX の方針からすれば見当違いもいいとこです。

2024-11-13 追記 POSIX 標準規格は ISO でも採用されておりレビューが行われています。ISO は POSIX 標準規格の問題として「Definitions」に含まれている要件を削除するように要求しました。この事実からも用語の定義は要件ではないことが明らかです。

https://www.austingroupbugs.net/view.php?id=1874

In their comment 052 on Issue 8 the ISO editors requested that any requirements stated in definitions be removed. The Austin Group's response was that a change would be considered for the next TC.

基本編

「ファイルの末尾の改行」とは何の話?

まず「テキストファイルの末尾の改行」が何の話か知らない人に向けて、どういう話なのかを簡単に説明します。「テキストファイルの末尾の改行」は実は正確な表現ではありません。正しくは「すべての行は行末に改行が必要」という話です。つまりこういうことです。(<LF> は改行の意味)

すべての行の行末に改行がある
line1<LF>
line2<LF>
line3<LF>

テキストファイルの末尾に改行がないというのはこういう状態のことです。

最終行だけ行末に改行がない
line1<LF>
line2<LF>
line3

テキストファイルの末尾に改行がないというのは、最後の行だけ行末に改行がないということです。一貫性がありませんよね? 一貫性がないデータは、それを扱うための例外的な処理が必要となる場合があり、しばしばバグを引き起こすことがあります。

Windows ユーザーはテキストファイルをファイル全体をひとかたまりとして見る傾向があると思いますが、Unix ユーザーは歴史的にテキストファイルを行の集まりとして見る傾向にあります。これは元々 Unix(1969年誕生)が CLI(コマンドラインインターフェイス)文化であり、行単位の命令でコンピュータを操作する OS として始まっているからです。Unix を操作するコマンドはファイルを行単位で処理していました。そのようになった理由はテレタイプというタイプライターのような見た目の端末を使っていたからです。テレタイプの出力はディスプレイではなく印刷されたら消すことができない紙に印字され、カーソルを上下左右に自由自在に動かして文字を消すような使い方はできませんでした。テキストエディタはそのような制限がある環境でも使える行単位で操作するラインエディタが使われていました。そのような時代ではテキストファイルを行の集まりと見るのは自然な発想です。

Windows の前身である MS-DOS(1981年誕生)の時代ではすでにディスプレイが当たり前となっており、CLI コマンドもそれほど高度なものではなかったためテキストファイルを行の集まりであるという考え方は希薄です。メールは 80 文字だか 76 文字だかで適度に改行を入れろと言われることがありますが、ワープロなどでは自動折り返しをうまく機能させるために長い文章でも改行を入れずに入力しますよね? 現代のテキスト文書は行の集まりではありません。特に単語をホワイトスペースで区切らない日本語では適度に改行を入れた文書を画面幅に応じて自動的に再構成するのが難しいため、適度な改行はあまり良くない結果をもたらします。

「テキストエディタの仕様」なんてない

私の意見は「テキストファイルの末尾に改行を入れた方が良い」 です。

ですが、テキストエディタはテキストファイルの末尾に改行を入れる仕様にしなければいけないわけではありません。テキストエディタは POSIX で定義された「テキストファイル」だけしか作成してはならないというのは単なる思い込みです。テキストエディタは「テキストファイルもどき」を作成したって良いのです。なんならバイナリファイルを作れるテキストエディタだってあるでしょうと。

例えば Visual Studio Code は(仕様変更されていなければ)デフォルトでテキストファイル末尾に改行を自動で入れることはありません。私はテキストファイルの末尾に改行を入れた方が良いと考えており、それをデフォルトにするべきだとも思いますが、Visual Studio Code がそうしなければいけない理由はありません。なぜなら Visual Studio Code は POSIX で標準化されたアプリケーションではないからです。POSIX 標準規格書に「Visual Studio Code は POSIX で定義されたテキストファイルだけを読み書きし、末尾に改行を入れなければならないと書いていない」ので、どのような仕様でも POSIX に違反することはありません。vi や ed や ex は OS のインターフェースとして POSIX で標準化されているため、vi や ed や ex の実装者は(POSIX に準拠したいなら)POSIX の仕様に従う必要があります。しかし vi や ed や ex 以外の「テキストエディタの仕様」なんてものは POSIX で規定されていません。安心してください。あなたが今までにない画期的で素晴らしい機能を実装したテキストエディタを開発したとして、そこに POSIX がしゃしゃり出てきて「POSIX が決めたとおりに作れ!そんな機能を追加するな!」と指図することはありません。あなたには独自のテキストエディタを作る自由があります。

さまざまな OS 間でアプリケーションの移植性を向上させる POSIX があるおかげで Visual Studo Code は Linux や macOS だけではなく FreeBSD でも動作します。そして、POSIX を凌駕する OS 間の互換性問題を吸収する技術によって、Visual Studo Code は「POSIX 環境ではない Windows」上でも動きます。しかし(vi や ed や ex とは異なり)Visual Studo Code 自体は OS のインターフェースとみなされていない(みなされるわけがない)ため POSIX の標準化の対象ではありません。そもそも GUI 関連は POSIX の標準化の対象外です。Visual Studio Code は POSIX 環境だけではなく Windows まで対応している、単なる「POSIX の想定範囲以上に移植性が高い」アプリケーションです。そこに POSIX がしゃしゃり出てきて「POSIX が決めた機能だけを使って作れ!Windows 対応機能を追加するな!」と指図することはありません。末尾に必ず改行を入れてしまうという制限があるテキストエディタに比べて、末尾の改行を省略することができるテキストエディタは、両方に対応した画期的で素晴らしい機能を持ったテキストエディタと解釈することもできます。テキストエディタの開発者には自分たちが作ったテキストエディタの仕様を決める自由があります。設定を変更すれば自動で末尾の改行を入れることができるのでこれで十分という考え方は理解できるものです。

一つ確かなことは「POSIX を持ち出してテキストファイルの末尾に改行をいれろと要求するのは不当である」ということです。ソフトウェアの開発者に POSIX に従わなければならない義務はありません。多くの開発者は「POSIX よりも優れたものを作るために」正当な理由があると考えれば POSIX から逸脱した実装を行います。例えば GNU は「他の組織が発行した標準を命令ではなく提案とみなし、標準を考慮するが従うわけではない」と立場を明確にしています。Linux だけではなく BSD 系 Unix も含め POSIX の認証を受けていないのは費用がかかるからだけではなく「POSIX で決められたことだけは絶対に守ろうという考えを持っていない」ことの現れでしょう。開発者に提案するのであれば POSIX などを持ち出さずに理由を言うべきです。「POSIX がそう言っているから」は理由ではなく「たかが POSIX ごときが言っていること」です。しかしそのような話の前に、そもそも POSIX はテキストファイルの末尾を改行にしなければならないと要求していないという話をする必要があります。

すべての行に改行が必要な本当の理由

一部のコマンドは行末に改行があることを前提にしており、行末に改行がない行は読み取れない(無視される)場合や、データとして正しく扱えない場合があります。ざっくり言えば「行末に改行がないとトラブルの元になりやすいから入れておけ」というのが、すべての行の行末に改行を入れたほうが良い本当の理由です。この話に POSIX は関係ありません。

行末に改行がない行を読み取れないツールは、バグとみなされることがありますが、実際には良い仕様である可能性があります。なぜならそれは「途切れた行」の可能性があるからです。例えば次のようなファイル名のパスが記述されたリストがあったとします。あるツールはこのリストを削除ファイルリストとして扱い、記述されたパスのファイルを削除するものとします。

/tmp/file1.txt<LF>
/tmp/file2.txt<LF>
/tmp/file3.txt<LF>

もしこのファイルが意図しない通信切断やディスク容量がいっぱいで書き込めなかったなどの何らかの理由で「途切れてしまった」とします。

/tmp/file1.txt<LF>
/tmp/file2.txt<LF>
/tmp/

こうなってしまうと、あるツールは /tmp ディレクトリ以下をすべて削除してしまうかもしれません。行末の改行はデータの終端文字と解釈することができます。終端文字まで読み取れれば行データは完全であると言えますが、もし終端文字が読み取れなければデータは不完全であるため、行末に改行がない行は無視するのが安全な仕様と言えます。つまりカチャカチャカチャ、ッターン!の「ッターン!」の段階で実行するのであって、カチャカチャしている間はまだ取り消しができる入力中だということです。

ソフトウェア業界の仕様はPOSIXが作ってるわけではない

この手の勘違いはだいたいが「POSIX が考えた仕様に従って Unix が作られている」という勘違いが原因です。Unix は POSIX が誕生するよりも前からあります。POSIX は Unix に共通する仕様をまとめたものです。その際に移植性があるアプリケーションを開発するためにどうしても仕方なかった部分に関しては POSIX が仕様を統一し、Unix は POSIX の仕様に合わせる形で調整を行いましたが、ほとんどの仕様は POSIX よりも前からあった Unix の仕様がベースです。Unix は POSIX に従って作られているわけではなく、逆に POSIX が Unix に従って作られているのです。

POSIXは「移植性がある OS のインターフェース」を標準化している団体ですが、ソフトウェア業界を牛耳っている団体ではありません。

  • POSIX が Unix/Linux の仕様を考えて作っているわけではありません
  • POSIX がプログラミング関連の規格を作っているわけではありません
  • POSIX がファイル形式の仕様を作っているわけではありません

POSIX はアプリケーションを移植したい人のために Unix 系 OS の仕様をベースとして OS の仕様を(作るのではなく)標準化しているだけです。標準化とは OS の実際の動作を仕様として定めることで、POSIX は Unix 系 OS の動きをとことん調べて、その動作を正確に文書化しています。プログラマは POSIX 標準規格書を読むことで、使ったことがない OS でもある程度仕様を把握することができ、移植性の高いアプリケーションを開発しやすくなります。POSIX はアプリケーションを移植可能にするにはどうすれば良いか? というプログラマの悩みに答えくれるガイドラインにすぎません。

今から新しい OS を作ろうと思っている人にとっては、おまけで POSIX が標準化した「移植性のある OS のインターフェース」と呼ばれているものを実装しておけば、新しい OS を Unix にしなくても POSIX 環境用に作られたアプリケーションを新しい OS で動かすことができます。POSIX が Unix を作るための標準規格ではなく、アプリケーションの移植性を高めるための標準規格というのは、こういう意味です。テキストファイルの定義も最終的にはアプリケーションの移植性を向上させることが目的の定義です。逆に言えば移植性を気にしないなら関係ない話です。

POSIX の目的は OS の機能を作ることではなく、アプリケーションの移植性を高めることです。つまりプログラマが開発したアプリケーションを Linux や macOS や FreeBSD や Solaris などで(ソースコードをコンパイルして)動かしやすくすることです。特定の OS 固有の優れた拡張機能が POSIX で標準化されることはないので、POSIX の機能だけで OS の能力を引き出したり、ハードウェアの性能を引き出すことはできません。これが POSIX の限界なのです。移植性と機能性や性能はトレード・オフの関係にあるため、移植性を重視するのであれば機能性や性能は犠牲になります。その問題を改善するために、多くの言語ではラッパーライブラリを開発することで、OS 固有の優れた拡張機能を同一のインターフェースで使えるようにして、生産性を犠牲にすることなく機能性や性能を引き出し POSIX の限界を乗り越えています。現在において POSIX は移植性を実現するための最低ラインに過ぎません。現実の実装はすでに POSIX を遥かに超えています。

誰もが自分のテキストファイルの定義を持っている

あなたにとってテキストファイルとはどのようなものでしょうか? おそらく多くの人は「人が読めるファイル」ではないかと思います。例えば端末に cat コマンドでそのまま出力して、それが読めればテキストファイルと考えるでしょう。しかし、それは POSIX の定義ではありません。例えば以下の出力は POSIX にとっては(C ロケール = POSIX ロケールで)テキストファイルです。

$ (LC_ALL=C; tr -d '\0' </dev/urandom | fold | head )
(�3�	Wmg>���O��@e5C�o�����,�����s�Z�H905�(/x}���
ŋ.m�:
     S���,ɷĹU8�r�������|��7M
                            NQoV]n�N����hްHUh��n���4�yČ6͌�|�M
.83�YI�\#�:���\�������O)��8��d�n"��S����-��l�$ᅺ	�B��k�
�7B>X.5W
        4OW<@�V��� ���۱��3�i{�h����U���lh�W�&h�
�]��6wxf�Qj�PlH.�( �N��\�(.�)����+��	�Ii<����H�S9Z*��PCjx�o�5�82�
|�ڎ[�4���.I6�3��z�QM����&�'e�`8R�q�������i��Eh"��s�
��ʷ�T�F.�ϧ���>}�,$�_[��:���Yp��,2�IK;�C|7��Ҏ�3���d#M2�����BJjB���݈
8��A���8j�4�c��<+�9�ˁ��-`R�/�+����C(����T=]
�`�o�1�T/��w��!V�5��z[J� ��|�U��:�Q�M�]�X�[9δ� ���\3�#i�7R�O�v���q(
 ݃�@Zo�k5�/k�4>��o;BXi��u:�3��?-yLtw��ϧ�nSy4�(,�&�?q-$��D�x`�U
@y�;�`V��29**�=��#c��;�T8u�I^���#���I��ʷq��0�C�J��4�_
                                                     �(-),����

6.2 Character Encoding より 0x80 - 0xFF の範囲も POSIX ロケールで characters であると解釈しています。

The POSIX locale shall contain 256 single-byte characters including the characters in Portable Character Set and Non-Portable Control Characters , which have the properties listed in 7.3.1 LC_CTYPE .

いかにもバイナリファイルのような出力ですが、そもそも POSIX はテキストファイルとバイナリファイルを区別していません。そのことは「3.387 Text File」の定義に明確に記されています。

POSIX.1-2024 does not distinguish between text files and binary files

「テキストファイル」の定義は人それぞれです。おそらく POSIX のテキストファイルの定義と同一の定義の人は少ないでしょう。多くの人が「末尾に改行がないテキストファイル」と呼んでいる事実からも明らかです。

POSIX のテキストファイル関連の用語の定義

POSIX の「テキストファイル」という用語の意味は 3. Definitions で定義されています。今回の話に関連がある定義をとりあえず抜粋します。

3.164 「ファイル」は書き込みまたは読み込み、またはその両方が可能なオブジェクト

3.164 File
An object that can be written to, or read from, or both. A file has certain attributes, including access permissions and type. File types include regular file, character special file, block special file, FIFO special file, symbolic link, socket, and directory. Other types of files may be supported by the implementation.

3.387 「テキストファイル」は0行以上の文字を含むファイル

3.387 Text File
A file that contains characters organized into zero or more lines. The lines do not contain NUL characters and none can exceed {LINE_MAX} bytes in length, including the <newline> character. Although POSIX.1-2024 does not distinguish between text files and binary files (see the ISO C standard), many utilities only produce predictable or meaningful output when operating on text files. The standard utilities that have such restrictions always specify ``text files’’ in their STDIN or INPUT FILES sections.

3.185 「行」は改行で終わる0文字以上の文字の並び

3.185 Line
A sequence of zero or more non-<newline> characters plus a terminating <newline> character.

3.58 「文字」は文字セットを表現する1バイト以上の並び

3.58 Character
A sequence of one or more bytes representing a member of a character set.

Note: This term corresponds to the ISO C standard term multi-byte character, where a single-byte character is a special case of a multi-byte character. Unlike the usage in the ISO C standard, character here has no necessary relationship with storage space, and byte is used when storage space is discussed. See the definition of the portable character set in Section 6.1 (on page 117) for a further explanation of the graphical representations of (abstract) characters, as opposed to character encodings.

3.61 「文字セット」はデータを表現するための異なる文字のセット

3.61 Character Set
A finite set of different characters used for the representation, organization, or control of data.

3.63 「文字列」はヌルバイトで終了するヌルバイトを含む文字の並び

3.63 Character String
A contiguous sequence of characters terminated by and including the first null byte.

3.172 「不完全な行」はファイルの最後の1文字以上の改行ではない文字の並び

3.172 Incomplete Line
A sequence of one or more non-<newline> characters at the end of the file.

これだけ箇条書きにすれば、感がいい人は「あぁ、これは用語集だ」と気づいたのではないかと思います。

POSIXは「テキストファイル」の規格を作ったのではない

POSIX にこのように「定義」されていることから、これがテキストファイルの仕様や規格だとよく勘違いされていますが、POSIX のテキストファイルの定義は仕様や規格ではありません。もし、これが仕様であるならば「shall(しなければならない)」や「should(したほうがよい)」のような言葉が含まれているはずです。これらテキストファイルの定義には shall も should も使われていません。だからこれらは仕様ではないのです。仕様ではないとしたらこれらは何なのでしょうか?

その答えは、これが書かれているページのタイトルのとおりです。「3. Definitions」でしたよね? そう、これは「POSIX が使用している用語の定義」なのです。ページにトップにはこう書かれています(太字は私によるもの)。

  1. Definitions
    For the purposes of POSIX.1-2024, the following terms and definitions apply. The Authoritative Dictionary of IEEE Standards Terms, Seventh Edition should be referenced for terms not definedin this section.

太字部分の訳: POSIX.1-2024 の目的のために、次の用語と定義が適用されます。

Rationale にはこのように書かれています。

The definitions in this section are stated so that they can be used as exact substitutes for the terms in text. They should not contain requirements or cross-references to sections within POSIX.1-2024; that is accomplished by using an informative note. In addition, the term should not be included in its own definition. Where requirements or descriptions need to be addressed but cannot be included in the definitions, due to not meeting the above criteria, these occur in the General Concepts chapter.

太字部分の訳: このセクションの定義は、本文中の用語の正確な代替として使用できるように記載されています。(補足: 解釈が難しいが残りの部分には「定義には要求事項を含まない」と書いているように読み取れるので、テキストファイルの定義には末尾に改行を入れなければならないという要求は含まれていないと解釈できるかもしれない)

POSIX は「用語の意味を正確に定義しないと仕様の話ができない」から最初に用語を定義しています。それが標準規格の最初の方にある「3. Definitions」です。この段階では仕様の話はまだ始まっていません。用語の定義だけを見せられても「それが POSIX のテキストファイルという用語の定義であることはわかった。それでテキストファイルがどうしたのかね?」と言うことしかできません。

POSIX はコマンドの動作を正確に説明するために用語の意味を定義したのであって、テキストファイルの仕様や規格を決めたのではありません。どこぞで「末尾が改行で終わらないファイルもテキストファイルと認めるべきだ」という意見を見かけましたが、それをテキストファイルの定義にしてしまうとコマンドの動作を正確に説明するのが面倒になってしまいます。なぜなら「このコマンドはテキストファイルを読み込むが、末尾の改行は必須である」と毎回説明しなければならなくなるからです。

POSIX の用語の定義は、POSIX 標準規格で使用するために定義されたものです。唯一の正しい定義というわけではありません。例えば農林水産省はイチゴは野菜と定義していますが、それは農林水産省の定義であって、私たちの一般的な定義ではありません。農林水産省の定義を理由に「イチゴは野菜と決まってるんだから野菜コーナーに置け!」とクレームを言っている人がいたとしたら、あなたはこう思うはずです。

お前がそう思うんならそうなんだろう お前ん中・・・・ではな (-□д□-)

ちなみに米国ではイチゴは果物ですが、キュウリやナスも果物だそうです。果物もテキストファイルも世間一般の定義はさまざまで統一されていません。だから仕様という厳密な話をするときには最初に用語の正確な意味を定義をする必要があります。POSIX の用語の定義は、POSIX ん中の狭い世界の定義でしかなく、POSIX 以外の世間一般のテキストファイルの定義にまで影響を与えるものではありません。

テキストファイルの定義をちゃんと読もう

POSIXの用語の定義を引用しているところはいくつもあるのですが、ちゃんと読んでいる?と思うことがよくあります。POSIX のテキストファイルの定義を箇条書するとこうなります。

  1. 文字が含まれた 0 行以上の行で構成されたファイル(空ファイルも含む)
  2. 行はヌル文字を含まない
  3. 行は改行を含めて LINE_MAX バイトを超えない
  4. POSIX ではテキストファイルとバイナリファイルを区別していない
  5. 多くのユーティリティはテキストファイルを操作するときにのみ予測可能または意味のある出力を生成する
  6. このような制限を持つ標準ユーティリティは STDIN または INPUT FILES セクションで常に「テキストファイル」を指定する

これらの項目はすべて重要です。テキストファイルの末尾に改行が必要というところだけを読んで、その他の重要項目を読み飛ばしていませんか?

1行が2048バイトを超えたらテキストファイルではない

POSIX のテキストファイルの定義では 1 行は LINE_MAX バイト以内です。LINE_MAX バイトの値は環境依存です。POSIX としては最低 2048 バイトということになっており、Linux、macOS、各BSD Unix、Solaris で調べた所、すべて 2048 バイトでした。したがって事実上 2048 バイトを超える行があったら、そのファイルはテキストファイルではないということになります。

LINE_MAX の調べ方
$ getconf LINE_MAX
2048

2048 バイトは UTF-8 で日本語の 1 文字を 3 バイトと仮定して計算すると 682 文字です。原稿用紙 1.5 枚分、技術書だと半ページ程度の量です。一段落の目安は多くても 300 文字ぐらいが限界なようなので普通のテキスト文書で問題になるとは思えませんが、CSV や JSON Lines のような 1 行単位で複数属性のデータを持つようなテキストベースのデータ形式の場合は 2048 バイトを超えることもあるでしょう。そのような長い行が含まれるファイルは POSIX 的にはテキストファイルではありません。しかし「テキストファイルは 1 行の長さを 2048 バイトまでにしろ!」と言っている人は、なぜだか見かけたことがありません。

ちなみに LINE_MAX の値をどうすべきかについては過去に議論になったようです。その詳細については C.1.3 Utility Limits を参照してください。一部の人々は 1 行の長さについて制限なしを求めましたが、歴史的なユーティリティは固定サイズのバッファを使用していたため、(C 言語で固定サイズのバッファの実装を無制限に作り変えるのは大変なので)その妥協案として当時としては大きめの 2048 バイトが選ばれたようです。今だともう少し大きくても良かったのではないかと考えるかもしれませんが、それが決められた 1990 年頃の性能が低いコンピュータでは、小さすぎずメモリ使用量の負担にならない現実的なサイズだったのでしょう。

LINE_MAX は、各コマンドが使用するバッファのサイズの要件ではありません。つまりバッファサイズを 2048 バイトにしなければいけないという意味ではなく、実際のコマンドはより多くのサイズを扱えるようにして構いませんし、そうすることが推奨されています。そのこともリンク先に明記されています。

it may not be a good idea to allocate blindly a buffer for an input line based on the value of {LINE_MAX}, for instance.

LINE_MAX の値は、POSIX コマンドを使用して移植性が高いアプリケーション(シェルスクリプト)を書こうと思っているプログラマが、(POSIX に準拠しているシステムで)安心してコマンドに渡すことができる 1 行の最大の長さだということです。

出力は2048バイト以内にする必要はない

LINE_MAX は入力時の固定のバッファサイズが由来の制限であるため、出力のサイズには関係しません。つまりコマンドの出力は 1 行が 2048 バイトを超えても構わないということです。1 行の出力サイズに制限はありません。

It should be noted that {LINE_MAX} applies only to input line length; there is no requirement in POSIX.1-2024 that limits the length of output lines.

POSIX では awksedpaste コマンドを例に、1 行の長さは入力サイズよりも長くなる場合があり、LINE_MAX を超える行に対処するのはアプリケーション(シェルスクリプト)の責任であることが明確に記されています。

Utilities such as awk, sed, and paste could theoretically construct lines longer than any of the input lines they received, depending on the options used or the instructions from the application. They are not required to truncate their output to {LINE_MAX}. It is the responsibility of the application to deal with this. If the output of one of those utilities is to be piped into another of the standard utilities, line length restrictions will have to be considered; the fold utility, among others, could be used to ensure that only reasonable line lengths reach utilities or applications.

ここから言えることは、移植性が高いシェルスクリプトを書こうとしている人は

sed '...' input.txt | awk '...'
  1. sed コマンドの入力(input.txt)の 1 行の長さは 2048 バイト以内ですか?
  2. awk コマンドの入力の 1 行の長さは 2048 バイト以内ですか?

を考慮しなければいけないということです。まあこんなこと、普通は誰もこんなこと想定しませんよね? しかし POSIX を持ち出して「テキストファイルは POSIX の定義に従って末尾を改行にしなければならない」と主張するのであれば、同じように「テキストファイルは POSIX の定義に従って 1 行の長さを 2048 バイト以内にしなければならない」と主張しなければいけないはずです。POSIX に準拠してシェルスクリプトを書くというのは、こういうことを考えながらシェルスクリプトを書くということです。

GNUコマンドにはサイズ制限はない

(おそらくすべての)GNU 版のコマンドの 1 行の長さは、事実上の無制限(搭載メモリ量やメモリモデル上の制限のみ)です。このことは GNU のドキュメントに明記されています。

Though grep expects to do the matching on text, it has no limits on input line length other than available memory, and it can match arbitrary characters within a line. If the final byte of an input file is not a newline, grep silently supplies one. Since newline is also a separator for the list of patterns, there is no way to match newline characters in a text.

GNU grep には搭載メモリ容量以外の制限はなく、末尾に改行がない場合は黙って改行を加える

For those who want to write portable sed scripts, be aware that some implementations have been known to limit line lengths (for the pattern and hold spaces) to be no more than 4000 bytes. The POSIX standard specifies that conforming sed implementations shall support at least 8192 byte line lengths. GNU sed has no built-in limit on line length; as long as it can malloc() more (virtual) memory, you can feed or construct lines as long as you like.

GNU sed には行の長さによる制限はない。(補足: POSIX によると 8192 バイトは パターンスペースとホールド スペースのサイズであり、4000 バイトとは歴史的な実装が使用しているサイズなので、GNU による説明は POSIX の説明と矛盾している)

Length of input record in bytes ULONG_MAX

GNU awk には入力行のバイト数の長さに制限はない。(その他の制限も事実上ない。詳細はリンク先へ)

If the final byte of an input file is not a newline, GNU sort silently supplies one. GNU sort (as specified for all GNU utilities) has no limit on input line length or restrictions on bytes allowed within lines.

GNU sort は行末に改行がない場合は黙って加える(この動作は POSIX でも規定されている)。また(すべての GNU ユーティリティで指定されているように)入力行の長さの制限はない

ということで、GNU コマンドを使っている限り、行の長さの制限はないようです。(詳しく調べてはいませんがその他の実装も行の長さの制限はない傾向にあるようです。)

もちろん制限がないのは GNU コマンドであって、OS 標準のコマンドを使って移植性が高いシェルスクリプトを書こうと考えている人は、サイズ制限を意識する必要があるでしょう。実際のコマンドに制限があるかどうかは、環境依存なので調べるしかありません。すべての環境を調べるという面倒なことをしなくてもいいように POSIX が最低のサイズを決めています。しかし、そうやって POSIX に準拠させようと苦労してシェルスクリプトを書くよりも、どの環境でも動く移植性が高い GNU コマンドをインストールしてしまったほうが簡単に高い移植性を実現することができます。

空ファイルは「テキストファイル」扱いすることに変わった

中身がないファイルは、はたして"テキスト"ファイルなのでしょうか? 哲学的な話ですが、POSIX においては空ファイルもテキストファイルとして扱うと定義されています。この定義は POSIX.1-2008 (Issue 7) で変更されており、以前は空ファイルはテキストファイルではありませんでした。

POSIX.1-2001 (Issue 6) まで ・・・ テキストファイルは 1 行以上

A file that contains characters organized into one or more lines.

POSIX.1-2008 (Issue 7) から ・・・ テキストファイルは 0 行以上

A file that contains characters organized into zero or more lines.

変更の経緯は(Austin Group > Aardvark > Aardvark Defect and Bug Reporting > XCU Defect reportsの)「Enhancement Request Number 106」で提起されており、理由を要約すると「diff コマンドで空ファイルをバイナリファイルとして比較したときの出力が行われると困る」「歴史的なコマンドは空ファイルをテキストファイルとして扱ってきた」の 2 点です。

diff コマンドはバイナリファイルとして比較した場合に次のような出力を行います。

$ diff data1.txt data2.txt
Binary files data1.txt and data2.txt differ

片方がテキストファイルで、片方がテキストファイル以外である場合、diff コマンドはバイナリファイルとして比較しなければなりません。空ファイルをバイナリファイルとして扱わなければならないのであれば、POSIX に厳密に準拠すると上記のような出力をしなければならず差分を出力することができなくなってしまいます。しかし実際の diff コマンドの実装は歴史的にそのような動作を行っておらず空ファイルをテキストファイルと扱っていました。

また、テキストファイルを扱う POSIX コマンドに対して空ファイルを読み込ませることができなくなってしまいます。例えば次のような例を考えてみます。

$ grep "error" /var/log/syslog | sort | uniq

sort コマンドや uniq コマンドはテキストファイルを入力するコマンドです。grep コマンドでログファイルの中から error という文字列が含まれる行を抽出したとき、error が見つからなければ当然 grep コマンドは何も出力しません。つまり空ファイルを出力します。空ファイルを uniqsort で処理しようと思ったとき、もし空ファイルをテキストファイルと認めないのであればコマンドの動作は不定となり、そのような使い方は移植性がないから避けなければならないことになってしまいます。歴史的なコマンドの実装は空ファイルをテキストファイルとして扱ってきており、空ファイルの入力は一般的な使い方であるため、POSIX はその現実を正しく標準化できていなかったことになります。これがテキストファイルの定義が変更になった理由です。

Enhancement Request Number 106の内容(変更の経緯)
_____________________________________________________________________________
OBJECTION                                       Enhancement Request Number 106
eggert:cs.ucla.edu             Defect in XCU diff (text vs binary) (rdvk#  6)
{20060612a}                             Tue, 13 Jun 2006 06:13:13 +0100 (BST)
_____________________________________________________________________________
Accept_X___    Accept as marked below_____     Duplicate_____     Reject_____
Rationale for rejected or partial changes:

ERN 106 (->interps) AI-192
This needs to be submitted down the interpretations track The standard is clear, standard is wrong, defect, concerns are being forwarded to the sponsors.

_____________________________________________________________________________
Page: 321  Line: 12380  Section: diff


Problem:

Edition of Specification (Year): 2004

Defect code :  1. Error

POSIX says that empty files are not text files, but common practice is to treat empty files as text files.  This causes problems, particularly with "diff", but also with other commands.

1. POSIX's exclusion of empty files from the set of text files requires behavior that contradicts all POSIX implementations that I know of.  For the following script executed in the POSIX locale:

         : > empty
         diff empty empty

POSIX requires that the output of "diff" must contain the word "differ" (see XCU page 321 line 12382).  I know of no diff implementation that does this; they all output nothing, which is what users expect.

2. Typically, diff implementations sample the first few bytes of a file.  If any bytes are zero, they treat the file as binary; otherwise they treat it as text.  In contrast, POSIX requires that "diff" must inspect the entire input file and strictly check for any encoding errors or long lines.

3. POSIX says that portable scripts cannot use standard utilities like 'grep', 'sed,', 'sort', etc. on empty files.  For example, the command:

    grep PATTERN FILE | sort | sed 's/$/./'

does not conform to POSIX if FILE contains no lines that match PATTERN.  Yet this sort of shell script programming is very common practice, and POSIX should not say that the behavior is undefined here.

If there is a good reason that empty files are not text files (compatibility with TENEX, perhaps? :-) then this should be clearly documented in the rationale.  However, I suspect that whatever reason may have existed long ago, is no longer valid.

 Action:

 In XBD page 89 line 2824 (definition of Text File), change from:

   A file that contains characters organized into one or more lines.

 to:

   A file that contains characters organized into zero or more lines.

 In XCU page 321 line 12380-12382 (Diff Binary Output Format), change
 from:

   In the POSIX locale, if one or both of the files being compared are not text files, an unspecified format shall be used that contains the pathnames of two files being compared and the string "differ".

 to:

   In the POSIX locale, if one or both of the files being compared are not text files, it is implementation-defined whether "diff" uses the binary-file output format or the other formats as specified below. The binary-file output format shall contain the pathnames of two files being compared and the string "differ".

 In XRAT page 32 lines 1232-1233, change:

   The definition allows a file with a single <newline>, but not a totally empty file, to be called a text file.

 to:

   The definition allows a file with a single <newline>, or a totally empty file, to be called a text file.

STDINまたはINPUT FILESセクションを読もう

「テキストファイル」の定義は、単なる用語の定義に過ぎません。コマンドがどういう動きをするのかというコマンドの仕様は、コマンドごとの STDIN または INPUT FILES セクションに記されています。したがってテキストファイルの定義だけを知ってもなんの役にも立ちません。

ちなみにテキストファイルを出力するコマンドは STDOUT や OUTPUT FILES セクションにそのことが記載されていますが、そのようなコマンドはまれです。つまり多くのコマンドは POSIX が定義するテキストファイル以外を出力する可能性があるということです。テキストファイルを出力すると明記されているのは nluuencode がぐらいです。sed コマンドは STDOUT には書かれていませんが、OUTPUT FILES はテキストファイルと書かれていますね(これは間違いのような?)。

POSIXコマンドの仕様を説明するための「テキストファイル」

POSIX が「テキストファイル」という用語の意味を定義した理由は「テキストファイル」という用語をコマンドの動作を説明するために使うからです。POSIX コマンド、つまり歴史的な Unix コマンドの多くはテキストファイルを扱うコマンドばかりだと考えているかもしれませんが、意外とそうではありません。入力ファイルとして任意のファイル形式(バイナリ形式)を入力できると POSIX で規定されているコマンドは結構あります。

任意のファイル形式を入力できるコマンド

コマンド ファイル形式 補足
cat 任意の形式
cksum 任意の形式
cmp 任意の形式
dd 任意の形式
diff 任意の形式
file 任意の形式
od 任意の形式
read 任意の形式
sh 任意の形式 冒頭部分はシェルが解釈可能なコード
split 任意の形式
tee 任意の形式
tr 任意の形式
wc 任意の形式

もちろんテキストファイルしか入力できないコマンドもあります。その一部には特定のオプションを指定したときだけ他の形式を扱える場合があります。

コマンド ファイル形式 補足
awk テキスト
bc テキスト
comm テキスト
csplit テキスト
ed テキスト
expand テキスト
fold テキスト 行の長さに制限なし(-b)
grep テキスト
iconv テキスト
join テキスト
more テキスト
nl テキスト
sed テキスト
tsort テキスト
tail テキスト 任意の形式(-c)
unexpand テキスト
uniq テキスト
xargs テキスト 任意の形式(-0)

テキストファイルしか入力できないように思えて、一部の制限が緩和されたテキストファイルもどきを扱えるコマンドもあります。

コマンド ファイル形式 補足
cut テキストもどき 行の長さに制限なし
ex テキストもどき 不完全な行の場合は改行を加える
head テキストもどき 行の長さに制限なし、任意の形式(-c)
vi テキストもどき 不完全な行の場合は改行を加える
paste テキストもどき 行の長さに制限なし
sort テキストもどき 不完全な行の場合は改行を加える

「任意の形式のファイル」を入力できるコマンド

catcksumcmpdddifffileodreadshsplitteetrwc は任意の形式のファイル(バイナリファイル)を入力できることが POSIX で標準化されています。これは任意の形式のファイルを入力した場合の動作が規定されているという意味ですが、その動作が納得できる仕様であるかは別問題です。例えば wc コマンドは末尾に改行がないファイルは 1 行少なくカウントされるかのような動作を行います。

# 末尾に改行があれば 3 行とカウントする
printf 'FOO\nBAR\nBAZ\n' | wc -l # => 3

# 末尾に改行がなければ 2 行とカウントする
printf 'FOO\nBAR\nBAZ' | wc -l # => 2

wc コマンドは末尾に改行がない行をカウントしないため、一般的には末尾に改行は必ず入れる必要があると言われています。しかし wc コマンドは単に改行文字の数を数えるだけなので、任意の形式のファイルを入力することができるというのが仕様です。ちなみにこの仕様は POSIX が考案したわけではありません。歴史的な wc コマンドの動作がそのようになっており、移植性のあるシェルスクリプトを書きたい人にとって役に立つ情報というのは、実際の wc コマンドの正確な動作です。

余談ですが、POSIX に対して「wc コマンドの仕様は合理的ではないから変更しろ」と要求する人がいます。しかし POSIX が末尾に改行がなくてもカウントしろと書いたとしても、誰もそんな仕様で実装していないのですから、移植性のあるシェルスクリプト書くのに役に立ちません。しかも POSIX はその動作に移植性があると嘘を書いていることになります。順番を間違えてはいけません。まず GNU や BSD 系 Unix や System V 系 Unix に訴えて、実装を変更させたあとで改めて POSIX に要求する必要があります。それが難しいからと言って「POSIX を悪用して実際の実装に対して変更するように圧力をかける」ことは許されません。

cat コマンドにも似たような問題があります。2つのテキストファイルがそれぞれ次のような時、

file1.txt
foo
file2.txt
bar<LF>
baz

2 つのファイルを結合したときの出力は次のようになります。

$ cat file1.txt file2.txt
foobar
baz             ← 末尾に改行なし

本来、cat コマンドはバイナリファイルを含むファイルを結合するコマンドなので、末尾に改行が含まれてない場合には、そのまま結合されてしまいます。

diff コマンドはテキストファイルを比較するだけのように思えますが、実際にはバイナリファイルを入力することができます。

$ head -c 1000 /dev/urandom > data1.bin
$ head -c 1000 /dev/urandom > data2.bin
$ diff data1.bin data2.bin
Binary files data1.bin and data2.bin differ

read コマンドは POSIX.1-2024 より前まではテキストファイルのみを読み込むという仕様でした。しかし POSIX.1-2024 ではその仕様が改められ、バイナリファイルやテキストファイルもどきを入力できるようになりました。その理由の一つは -d オプションの追加により行区切りの文字を(例えば \0 に)変更できるようになったからですが、ずっと前(おそらく Bourne シェルの時代)から、末尾に改行がないテキストファイルもどきを入力することができました。つまり、以前のPOSIX の標準化の内容は(テキストファイルしか扱えないという)必要のない制限があったということです。

# シェルで実行した cat コマンド相当の実装
shcat() {
  # readコマンドは改行を読み取ったら真、読み取らなかったら偽となる
  while IFS= read -r line; do
    printf '%s\n' "$line"
  done

  # 末尾に改行がないテキストファイルを読み取ることができる
  # (変数に末尾に改行がない最終行を読み取っている)
  printf '%s' "$line"
}

sh コマンド(シェル)はバイナリファイルを入力することができます。ただし冒頭部分はシェルが理解できるコードでなければなりません。つまりこの仕様はシェルスクリプトの後半部分にシェルが実行しないバイナリデータを埋め込んで利用するというテクニックを禁止しないための仕様です。

tr コマンドはテキストファイルを入力するコマンドのように思えるかもしれませんが、実際にはデータをバイナリとして処理することができます。これは入力ファイルに含まれた \0 を削除したりするために使うことができます。\0 が含まれたファイルはテキストファイルではありません。

$ tr -d '\0' data.bin

「テキストファイル」しか入力できないコマンド

awkbccommcsplitedexpandfoldgrepiconvjoinmorenlsedtsorttailunexpanduniqxargs はテキストファイルしか入力できないという制限があるコマンドです。テキストファイル以外(バイナリファイルやテキストファイルもどき)を入力した場合の動作は POSIX では規定されていないため、最終行の末尾に改行がない場合に、その行は無視されてしまっても文句は言えません。これらのコマンドのうち foldtailxargs は特定のオプションを指定したときに一部の制限が緩和されます。

これらのコマンドのうち、特によく使うのは awkgrepsedtailuniqxargs あたりでしょう。これらは POSIX の規定ではテキストファイルしか入力できないので注意してください。これはテキストファイル以外を入力した場合の動作は移植性がないという意味です。テキストファイルしか入力できないので、移植性があるシェルスクリプトを開発したいのであれば、入力ファイルの 1 行の長さは 2048 バイト以内にし、末尾には改行を必ず入れる必要があります。

ただし GNU コマンドのような一部の実装では、これらの制限が緩和されている場合があります。例えば GNU grep は末尾に改行がない場合に黙って追加することがドキュメントに明記されています。Solaris 11 の /bin/grep (POSIX 準拠ではない)は末尾に改行がない場合に改行を追加しないようですが、/usr/xpg4/bin/grep (POSIX 準拠版)追加するようです。AIX 版の grep では末尾に改行がない場合に追加されるようですが、ドキュメントには「入力ファイルの終わりには、改行文字を付けてください。」と書いてあるので、末尾に改行がないファイルを入力するのは避けたほうが良いでしょう。

「テキストファイルもどき」を入力できるコマンド

cutexheadvipastesort はテキストファイルもどき、つまり POSIX が定めたテキストファイルの条件を満たさない(がバイナリファイルよりも制限された)ファイルを入力した場合の動作が規定されているコマンドです。

cutheadpaste は 1 行の長さに制限はありません。head コマンドは -c オプションを指定したときに任意の形式を扱うことができます。head コマンドの対照コマンドとも言える tail コマンドには 1 行の長さに制限があるので注意してください。head コマンドや tail コマンドの(C 言語での)実装を考えれば、head コマンドに長さの制限を持たせないのは簡単で、tail コマンドは難しいことが想像できるでしょう。だから実際の実装でこのような制限ができてしまい、移植性があるシェルスクリプトを開発できるようにするため、その動作を POSIX で明文化する必要がありました。

exvisort は不完全な行を入力した場合には末尾に改行を加えることが POSIX で標準化されています。行を並び替える sort コマンドは、末尾に改行を加えないと意味のない出力を行ってしまうため、歴史的な実装はどれも末尾に改行を加えたのでしょう。

末尾に改行がないデータを

foo<LF>
bar<LF>
baz

末尾に改行を加えずにソートすると、次のようになってしまう。

bar<LF>
bazfoo<LF>

ただし Solaris 11 版や AIX 版の sort では改行を加えたときに警告が出力されます。POSIX には(どの OS の話かは書かれていませんが)警告を出力する場合があると書いてあるので、ちゃんと POSIX を読んでいればこの挙動に気づくことができます。

$ printf 'FOO\nBAR\nBAZ' | ( PATH=$(getconf PATH); sort )
sort: missing NEWLINE added at end of input file STDIN
BAR
BAZ
FOO

これらのコマンドの存在は、末尾に改行がないテキストファイルもどきを作成することを POSIX は禁止していない証拠でもあります。もしテキストファイルもどきの作成を許さないのであれば、単に「入力ファイルはテキストファイルのみである」という仕様にすればよかったはずです。テキストファイルもどきが入力された場合の動作を標準化しているということは、移植性がある機能として使用して構わないと言っているのと同じことです。

diffやGitHubは禁止ではなく警告している

末尾に改行がないファイルを diff コマンドで比較したときに「\ No newline at end of file」と出力されたり、GitHub で「⛔」と表示されているのを見て、なにか間違ったことをしたんじゃないかと焦っている人がいるようですが、これは単なる警告です。diff や GitHub が「間違っていない?」と親切に教えてくれているだけです。末尾に改行がないファイルを禁止しているわけではないので、正当な理由がある場合は無視しても構わないものです。

「\ No newline at end of file」の役割

diff コマンドでファイルを比較したときに「\ No newline at end of file」(「No newline at end of file」の部分は翻訳される可能性がある)と出力されることがありますが、これは末尾に改行がないテキストファイルを作ってはいけないという意味ではなく、単に末尾に改行がないことを知らせているだけです。もしこの出力がなければ次のようになってしまい、違いがあると出力されているのに何が違うのかわからないことになってしまいます。

$ diff -u file1.txt file2.txt
--- file1.txt   2024-08-03 07:18:01.797900284 +0900
+++ file2.txt   2024-08-03 07:17:57.005895715 +0900
@@ -1,3 +1,3 @@
 foo
 bar
-baz
+baz

「\ No newline at end of file」という出力があることで、変更前のファイルには行末に改行がなく、変更後のファイルには行末に改行があるとわかります。

$ diff -u file1.txt file2.txt
--- file1.txt   2024-08-03 07:18:01.797900284 +0900
+++ file2.txt   2024-08-03 07:17:57.005895715 +0900
@@ -1,3 +1,3 @@
 foo
 bar
-baz
\ No newline at end of file
+baz

diff コマンドの出力結果は、patch コマンドの入力としてファイルを変更するために使用されています。もし「\ No newline at end of file」という出力がなければ、patch コマンドで末尾の改行を削除することができなくなってしまいます。末尾の改行を削除したいことはないかもしれませんが、patch コマンドの役目としては正しい仕様とは思えません。

実は Solaris 版や AIX 版の diff コマンドは「\ No newline at end of file」を出力する仕様はなく(代わりに標準エラー出力に警告が出力される)、patch コマンドもこの出力を受け付けません。調べたのですがどうやら POSIX では「\ No newline at end of file」の仕様は標準化されていないようです。この仕様の発祥がどこかは調べきれませんでした(おそらく GNU diffです)が、BSD 系 Unix でも広く採用されており、移植性を高めるために標準化したほうがよいだろうと私は考えています。

GitHubが「⛔」(進入禁止)を使う理由

GitHub ではテキストファイルの末尾に改行がないときに「⛔」という絵文字を使って、そのことを知らせています(補足: おそらくファイルタイプ?拡張子?を見ていて .txt の場合には表示されない)。この絵文字は威圧感を与え、間違っているんだから必ず改行を入れろというふうに解釈する人がいるようですが、これは「\ No newline at end of file」と同じで、ただ末尾に改行が含まれていないと警告して知らせているだけです。

なぜこれを禁止の意味に捉えてしまうかと言うと、絵文字の意味が「進入禁止」だからです。「進入禁止」なのだから、禁止の意味に捉えてしまうのは自然です。しかしこれはただの警告です。なぜ GitHub はただの警告に禁止のマークを使用しているのでしょうか?

実は私はある仮説を立てています。侵入禁止は英語で「DO NOT ENTRY」(略して「NO ENTRY」)、または「DO NOT ENTER」(略して「NO ENTER」)です。知っての通り ENTER は改行を入力するためのキーなので「⛔」は「改行なし」と解釈できないこともありません。「NO ENTER」の用法は少ないようですが、まったくないというわけではないようです。

日本人にとっては「進入」と「改行」は結びつかないので「禁止」のイメージしか残りませんが、英語では NO ENTER をイメージするのではないでしょうか? 「⛔ は NO ENTER!」 この記事を書いた理由はこれが言いたかったからです。(嘘ですが)

さいごに

はぁ、やっと前から書きたかったこの記事を書き上げました。みんな末尾に改行が必要な理由として、POSIX がーとか標準規格がーって言ってるわけですが、みんなそれで納得してるんでしょうか? POSIX が言ってることは結論です。なぜ POSIX がそう言っているのか、なぜテキストファイルの末尾に改行が必要なのか、みんなその理由まで考えていないようです。

それにしても面倒な話でしょう? POSIX に準拠してシェルスクリプトを書くべきだなどと安易に言う人がいますが、本当に POSIX に準拠して書くのであれば、1 行の長さ 2048 文字問題への対処を考える必要があります。はっきり言ってそんなことやってられませんよ。制限がないと明確にしている GNU コマンドをインストールしたほうが簡単です。このような問題はシェルスクリプト自身の問題ではありません。シェルスクリプトの言語(シェル言語)自体に 1 行の文字列の長さの制限や末尾に改行を入れなければならないという要件はありません。これらはコマンドが持つ制限や仕様です。POSIX は現在も重要ですが、それは移植性のあるアプリケーションを開発した人にとっては重要という意味です。それなのに移植性のことを気に必要がないのに「POSIX がそう決めているからそれが正しい」で考えるのをやめる人が多いように思えます。

すべてのテキストファイルが末尾に改行が必要なわけではありません。例えば JSON ファイルのようにデータの終わりが構造から読み取れる場合には、末尾に改行をいれる必要はありません。ただし、その JSON ファイルの行数を wc コマンドで数えたりすることが想定されるのであれば、末尾に改行を入れたほうが良いでしょう。大抵の場合は末尾に改行を入れていたほうが安全なので入れることを推奨しますが、テキストファイルの末尾に改行を入れなければいけないかどうかは使用するツール次第です。初期の Unix は(予算を得るために)文書編集システムという体で開発が始まったため、テキスト文書を扱うコマンドが初期に作られました。POSIX のテキストファイルの定義がこのようになったのは、(複雑な構造を持つ JSON などのテキストベースのデータ構造ではなく)多くの行単位のテキスト文書を扱うコマンドを標準化する必要があったからです。

POSIX は POSIX で標準化しているツール(POSIX コマンド)の動作を説明する必要があるからテキストファイルという用語をあのように定義しただけで、末尾に改行がないテキストファイルもどきを作ってはいけないとは言っていません。POSIX コマンドを使わない人にとっては POSIX がなんと言っていたとしても関係ない話で、自分が使うツールが末尾に改行がないファイルを期待したとおりに扱えるかを気にすれば十分です。ただし他の人はそのファイルを POSIX コマンドを使って扱うかもしれないので、特に理由がない限り末尾に改行を入れておいた方が良いというのはそのとおりです。面倒なので省略しましたが、C 言語のソースコードも昔は末尾に改行が必要でしたが、C++ の新しめの規格では末尾に改行がなくても良くなったようです。こういうのも昔の C 言語コンパイラの一部には末尾の改行が必須の実装があっったためであり、現在の C++ コンパイラの実装は末尾に改行がない場合に改行を自動的に追加するようになったからなのでしょう。

私が Visual Studio Code はデフォルトで末尾に改行を入れるようにしておくべきだと思う理由は、どうせ Visual Studio Code なんて高度なテキストエディタを使う人はプログラマぐらいでしょ?という考えからです。多くのプログラマは POSIX コマンドを使い移植性も気にする傾向なので、デフォルトの設定をプログラマ向けにしておけば面倒ではなくなります。勝手に末尾に改行が加わり困る自体になったとしてもプログラマなら自分でどうにか対処できるでしょう? でも素人向けのテキストエディタであれば、自分で改行を入れたときだけ入るほうが直感的な動作かもしれません。おそらく Visual Studio Code はデフォルトを素人向けと定義し「プログラマなら自分で設定を変更することぐらいできるだろ!」という考えなのかもしれませんね。デフォルトの設定はプログラマにとっては少々不便ですが、設定を変更すればいいだけのことに目くじらを立てる意味がわかりません。ちなみに個人的には、ステータスバーに末尾の改行の状態がわかるような表示を加え、保存時に改行を加えるかの設定をステータスバーから簡単に変更できるようにすればよいのではないかと考えています。

テキストファイルの末尾に改行を入れたほうが良いという結論に変わりはないのですが、この話に POSIX なんて関係ないのですよ。入れたほうが良い理由は単に困る可能性が高いからです。

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?