POSIXコマンド出力のインデント・カラム間の「空白の数が環境によって異なるのはPOSIXに準拠した動作」であるという話

## はじめに

Unix / Linux のコマンドは POSIX に準拠しているコマンドであっても、そのインデント幅やカラム間の空白の数は環境によって異なります。しかしこれは POSIX でちゃんと定義されている動作です。空白の数は未定義、つまり環境によってバラバラですよと定義されています。

## インデントやカラム間の空白の数は環境によって異なる

もしかしたらコマンドの出力のインデント幅やカラム間の空白の数が異なるということを知らない人がいるかも知れないので例を紹介します。

```sh
# Linux (GNU) 
$ wc file*
     1      9     64 file1
    10     90    640 file2
   100    900   6400 file3
  1000   9000  64000 file4
 10000  90000 640000 file5
 11111  99999 711104 total

# macOS
$ wc file*
       1       9      74 file1
      10      90     740 file2
     100     900    7400 file3
    1000    9000   74000 file4
   10000   90000  740000 file5
   11111   99999  822214 total
```

このようにインデントやカラムの幅（空白の数）は環境によってバラバラです。

## POSIX の定義はどうなっているのか？

意外に思われる方もいるかも知れませんが、コマンドの出力形式の多くは POSIX で標準化されています。これが [wc](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/wc.html) コマンドの出力の定義です。

> By default, the standard output shall contain an entry for each input file of the form:
> 
> "%d %d %d %s\n", \<newlines\>, \<words\>, \<bytes\>, \<file\>

補足 `<newlines>` は改行の数、すなわち行数のことです。

あれ？ 改行、単語、バイト、ファイル名の間はそれぞれ**スペース一つじゃん？**って思った人へ

**「一体いつから、これが `printf` の書式だと錯覚していた！？」**

似ていますが違います。まあ私も最初はそう勘違いしたんですけどね。でもそれだと辻褄が合わないわけです。

この書式の意味は「[File Format Notation](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap05.html)」および「[Rationale - File Format Notation](https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap05.html)」で定義されています。後者の説明が分かりやすいですね。

> The notation for spaces allows some flexibility for application output. Note that an empty character position in format represents one or more \<blank\> characters on the output (not white space, which can include \<newline\> characters).

スペースの表記によりアプリケーションの出力にある程度の柔軟性を持たせることができます。なお書式の空文字の位置は、1 つまたは複数の空白文字を意味します（改行を含むホワイトスペースではありません）。

> The treatment of integers and spaces is different from the printf() function in that they can be surrounded with <blank> characters.

整数やスペースの扱いは `printf()` 関数とは異なり空白文字で囲むことができます。

> This was done so that, given a format such as:
> 
> "%d\n",\<foo>
> 
> the implementation could use a printf() call such as:
> 
> printf("%6d\n", foo);

これは `"%d\n",<foo>` という書式で、`printf("%6d\n", foo);` のような実装ができるようにするためです。

ということで

POSIX の仕様では `"%d %d %d %s\n", <newlines>, <words>, <bytes>, <file>` という書式で示される**インデントやカラム間の空白の数はいくつでも良い**んです。

探せば例外的に出力の空白の数がきっちり決められてるコマンドがあるかもしれませんが、基本的に空白が入ってる箇所の空白の数は環境によってバラバラということです。

## 「POSIXが詳細仕様を規定しきれなかったから各自の判断で実装した」という勘違い

いや、規定してますよね？　空白の数はバラバラでよいという仕様です。

「バラバラでいいなんて未定義と一緒だ。ちゃんと決めなかった POSIX が悪い！」

と言うような方は時系列をわかっていません。POSIX が仕様を作ってそれを元に OS ベンダーが実装しているのではありません。POSIX が詳細な仕様を決めてなかったから OS ベンダーが自分たちの判断でバラバラに決めたのでもありません。最初に OS ベンダーが実装していて、互換性がなくなってきて問題になってきたから生まれたのが POSIX です。OS ベンダーがバラバラにコマンドを実装していたから、そのコマンドの実装の違いを詳細に調べながら**後から**標準化したのが POSIX です。時系列をちゃんと把握しましょう。

そもそも POSIX が決めている標準規格を「仕様」と捉えるのが間違っていて、これは移植性が高いソフトウェアを作るためのガイドラインです。つまり「空白の数は未定義である」という言葉の真の意味は「移植性が高いソフトウェアを作るのなら、空白の数は実装によって異なるから気をつけてソフトウェアを開発しなさい」と言っているのです。

もしここで詳細を決められないからと標準規格に何も書かなかったら、ソフトウェア開発者はそれこそ困ってしまいます。空白の数が決まっているのか決まっていないのか分かりません。だから POSIX は「未定義である」と定義しているのです。「未定義である」と定義されていることによって POSIX を参照すれば、ここは実装によって挙動が違うんだなということがわかります。

それにそもそも POSIX はコマンドによって違いが有ることに気が付かなかったなんてありえないんですよ。**環境によって違いがあって困るという問題を解決するために作られた標準化団体は、標準規格を作るために細かい違いを調べてるんだから、こんな実行すればすぐに分かるような簡単な違いに気が付かないわけがないでしょう？** 気がついた上で、ああこれは標準規格に「互換性問題に気をつけなさい」と書かないといけないなと思ったから「未定義である」と定義しているわけです。

「POSIX は標準規格を作っている団体なんだから仕様を一つに決めるのが仕事だ！」という考えも間違いです。少なくとも POSIX はそういう団体ではありません。もし仕様を一つに決めてしまったら、すでにある多くの実装が POSIX 違反となってしまいます。POSIX は自分たちで仕様を決めて OS ベンダーに押し付ける団体ではありません。ただ現実の姿を標準化しているだけです。もし仕様を押し付けるような団体だったら誰も POSIX に準拠しようとは思わなかったでしょう。すでにある実装はなるべくそのままで POSIX 準拠になるように標準規格を作ったのが POSIX です。そういう方針である以上、将来これらの仕様の違いが POSIX によって統一されることもありませんのでそれを POSIX に期待するのも無駄ということです。

## まとめ

POSIX コマンド出力のインデント・カラム幅が環境依存なのは POSIX に準拠した動作です。POSIX の標準規格にちゃんと「空白の数は未定義である」と書いてあります。「未定義である」と定義することと何も書かない（仕様漏れ）は意味が全く違います。「未定義である」と定義することで、それが現実の姿であり互換性問題に気をつけてソフトウェアを開発しなさいということを POSIX は言っているのです。POSIX の標準規格は今も改訂が続けられている通り、完璧なものではありませんが、それでもこんな初歩の仕様が漏れているなんてことはありません。