echoコマンドの移植性が低い歴史的理由とPOSIXの改定方針 ～ 次期POSIXでbashのechoはPOSIX準拠になる！

## はじめに

実は bash に組み込まれた `echo` コマンドは POSIX に準拠していません。しかし 2023 年に予定されている次期 POSIX (Issue 8) の改定で、POSIX 準拠の動作になります。🎉🎉🎉

私のこの言い方には違和感を感じるかもしれません。「POSIX に違反している bash が問題点を修正して、POSIX に準拠させるのではないのか？」と。いいえ違います。POSIX 側が仕様を修正することで、bash は何も変更せずに過去のバージョンも含めて POSIX に準拠するようになります。面白いですね。

この記事は **`echo` コマンドの移植性の問題の歴史**を振り返りながら、それを例に **POSIX 標準化団体がどのような方針で標準規格を策定（改定）しているかを伝える**一石二鳥な記事です。シェルスクリプトと POSIX の関係について知りたい方におすすめします。

この記事の内容に関して以下のページを主に参考にしています。

- [The echo that supports -n is Bell Labs Unix echo, not "BSD echo"](https://www.austingroupbugs.net/view.php?id=1206)
- ["echo" specification doesn't reflect current implementations (missing -e, -E and - handling)](https://www.austingroupbugs.net/view.php?id=1222) 
  - 翻訳: `echo` の仕様は現在の実装を反映していない（`-e`, `-E`, `-` の取り扱いが抜けている）
- [echo and printf behaviour](https://www.in-ulm.de/~mascheck/various/echo+printf/)
- [Why is printf better than echo?](https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo)

## こんなコードは移植性が低い

以下のように変数を `echo` して別のコマンドで処理をするというコードを良く見かけますが、変数 `value` に任意の文字列が入る場合は`echo` コマンドの仕様に伴い移植性の問題が発生します。

```sh
value=$(echo "$value" | sed ...)
```

また以下のようなコードも問題があります。

```sh
echo -n 'Hello'
echo -e '\033[31m RED \033[m'
```

bash など特定のシェル専用に書くというのであれば構わないと思います。ただし移植性が低いというのは知っておいてください。シバンを `/bin/sh` にしていたりすると開発環境と異なる環境で実行した場合に期待した通りに動作しないことがあります。理由はこの記事を読むとわかるでしょう。

## POSIX とシェルスクリプトの移植性に関する基礎知識

`echo` コマンドの仕様は POSIX で標準化されています。標準化されているコマンド = 移植性が高く安心して使えるコマンドと勘違いしている人が多いようですが実際はそうではありません。POSIX で標準化されるといのは、統一されたインターフェースが策定されたという意味ではなく、アプリケーション（シェルスクリプト含む）の移植性を高めるための、私達が知らなければいけない情報が文書化されただけにすぎないからです。

- ❌ 間違った POSIX 標準規格の使い方
  - POSIX が `echo` コマンドの仕様を作った
  - ⇒ 各実装が POSIX に準拠するように修正する
  - ⇒ POSIX に準拠した `echo` はどこでも同じ動きをするようになる
  - ⇒ `echo` は移植性が高いから**私達は何も考えずに使える**
- ⭕ 正しい POSIX 標準規格の使い方
  - 各実装でそれぞれ独自仕様を持った互換性が低い `echo` コマンドができた
  - ⇒ POSIX はどの機能が互換性がある動作なのかを文書化した
  - ⇒ POSIX はどの機能が互換性がない動作なのかを文書化した
  - ⇒ POSIX を参照し**私達は互換性に気をつけて書くことで移植性を高めることができる**

アプリケーションの移植性を実現するのに「誰が何のために努力しなければいけないのか」がポイントです。互換性に気をつけるというのは、各 OS 間の互換性問題に振り回されるということです。努力は大切ですが、POSIX を参照する事態というのは実装の違いに対応するために努力をする時であり、新しいものを生み出すために努力をする時ではありません。**別の環境で動くようにするためだけに、各環境の互換性問題に苦しみながら書かなければいけない世界は私は嫌です**。本来は POSIX なんて読まなくても開発できるようになるべきです。努力せずとも別の環境で動くようになれば OS の違いを調べるために POSIX を参照する必要もなくなります。シェルスクリプト以外の言語の多くはすでにそれを実現しています。POSIX を読んで移植性問題に詳しくなってもプログラマとして成長しているわけではなく、あんな罠こんな罠という豆知識を知ってるというだけです。シェルスクリプトの世界が変わってしまえば、それまでに覚えた豆知識は役に立たなくなります。私はシェルスクリプトの移植性問題をなくして、自分が覚えた移植性に関する知識を無駄なものにしようとしています。そうすることで他のプログラマが移植性問題に苦しまなくて良い世界を作るのが目的です。しかしながら、まだそのような世界にはなっていないので、このような記事も必要でしょう。

誰かが新しく `echo` コマンドを開発（再発明）する場合には POSIX を参照して作ることになると思いますが、POSIX を参照して作られた `echo` はごく一部です。一部を除き `echo` は POSIX で仕様が標準化されるよりも前に作られました。`echo` は仕様がなにもない所で生まれ複製され改良され、POSIX が誕生したのはその後です。`echo` コマンドに限った話ではありません。POSIX で標準化されている仕様の大部分は POSIX が考えて作り出したものではありません。POSIX は後からやってきました。この順番を間違えないようにしてください。

後からやってきた POSIX が「互換性が低いから俺らが仕様をきっちり決めてやる！ お前らは、俺らが考えた仕様に従って変更しろ！」などと身勝手なことを言い出したら反発されます。そのような標準規格は誰も採用しません。なにより仕様を統一するために変更してしまったら、既存のアプリケーションが壊れて動かなくなってしまいます。アプリケーションの移植性を高めるための標準規格なのに、アプリケーションが動かなくなるようでは本末転倒ですよね？ POSIX の基本方針は「すでにある OS やシェルやコマンドの仕様は変更しなくて良い」です。実際には何も変更せずに高い移植性を実現するのは不可能であったため、特に最初に POSIX が策定された段階では互換性がない変更を要求せざるを得ませんでした。しかしそれ以降の POSIX の改定では、可能な限り既存の実装を変更しなくて良いという方針を貫いています。これは「**現在互換性がない部分は、今後も互換性がないまま改善されることはない**」ことを意味しています。

互換性がない部分が改善されないのに POSIX に何の意味があるんだ？と思うかもしれませんが、POSIX 文書に「互換性がないと書いてある」ことが重要なのです。私たちは POSIX を参照することで互換性がないことを知り、その機能を使わなかったり代替手段を使ったり互換性問題を吸収するラッパーを開発したりすることで、アプリケーションの移植性を「私達自身の手で高めることが可能になる」というのが POSIX の価値です。アプリケーションの移植性を高めるには（OS 開発者も努力しますが）私達が努力しなければいけないのです。したがって POSIX の標準化の対象である OS の基本機能（POSIX API や POSIX コマンド）を直接使ってアプリケーションを開発するのは本質的に大変な作業ということになります。しかも大変な割に実現できることは別の環境でも動くというだけで新しいものは何も生まれません。今の時代、他の言語では多くの環境で動くというのは当たり前にできている事なので、シェルスクリプトでどの環境でも動くようにするにはどのように書くか？という（バッド）ノウハウが必要だったり、他の環境でも動くことが自慢になるようなレベルでは時代遅れなのです。

残念ながら今はまだシェルスクリプトの移植性は低いので、**どの環境でも動くようにしたいのであれば**（注意 必ずしもそうする必要はありません）POSIX を参照して自分で努力するしかありません。そうです。POSIX はどの環境でも動くようにするために読むものなのです。POSIX 標準規格を Unix 系 OS の仕様書だと考えるのは正しくありません。アプリケーション（シェルスクリプトを含む）の移植性を高めるために読まなければいけないガイドラインと考えるほうが適切です。POSIX のインターフェースは統一されていませんし将来統一されることもありません。だから OS の開発者だけではなく必要な場合にはアプリケーションの開発者が読んで違いを知らなければならないのです。POSIX は Unix / Linux の仕様がどうなっているのかという現実の姿を伝えています。Unix / Linux が後方互換性を保ちながら進化し続ける限り、POSIX はその最新の姿を反映させるために改定しつづけるでしょう。15 年ぶりに行われる POSIX Isseu 8 での大幅な改定では、この 15 年の間に何が変わった（普及した）のかが明らかになっています。

## `echo` コマンドの歴史

### 初期の Unix による分岐

`echo` コマンドは POSIX で標準化される 20 年も前に誕生し、無秩序に仕様変更および拡張されました。Version 6 Unix (1975) 時点ではオプションにもエスケープシーケンスにも対応していませんでした。現在 `-n` オプションは改行しないという意味で広く知られていますが、最初に実装された改行しない機能は `\c` というエスケープシーケンスでした。`\c` はあまり知られていないと思いますが、この文字以降の文字は改行も含め何も出力しないという意味です。現在の `echo` や `printf "%b"` などでも使用可能なので実際に動かしてみることができます。`\c` は Version 6 Unix と Version 7 Unix の間に作られた本流の研究 UNIX とは異なる [PWB/Unix](https://ja.wikipedia.org/wiki/PWB/UNIX) 1.0 (1977) で最初に実装されました。一方で Version 7 Unix (1979) ではエスケープシーケンスに対応せず、改行しないオプションとして `-n` が追加されました。（[ここ](https://www.austingroupbugs.net/view.php?id=1222)には `\c` が劣っている設計だったため Dennis Ritchie によって `-n` に修正されたというコメントがあるが、もう少し信頼性が高そうな情報を知りたい）

```sh
# Version 6 Unix までの `echo`
#   オプションもバックスラッシュも非対応
echo 'Hello'

# PWB/Unix 1.0（Version 6 Unix からの派生版）
#    \n, \c, \\, \0xx が実装
#    \c 以降は改行も含めて何も出力しない = 改行しない
echo 'Hello\c' 
echo 'Hello\cABC' # ABC も出力されない

# Version 7 Unix
#   改行しない -n オプションが実装
echo -n 'Hello'

# 補足 現在のシェルでも以下の方法で \c を使える
echo 'Hello\cABC'        # dash, zsh の場合
echo -e 'Hello\cABC'     # bash, ksh の場合
printf '%b' 'Hello\cABC'
```

Version 7 Unix 以降、AT&T の UNIX は二つに分岐します。一つは商用 UNIX への道で System III (1981)、System V (1983) 〜 へと続きます。もう一つは研究用 UNIX ([Research Unix](https://en.wikipedia.org/wiki/Research_Unix)) への道で Version 8 Unix (1985) 〜 Plan9 (1992) へと続きます。

Version 7 Unix では改行しないオプションとして `-n` が実装されましたが、System III は PWB/Unix の仕様を採用し、`\c`, `\n`, `\\`, `\0xx` に加えて `\b`, `\f`, `\r`, `\t` が追加されます。`\a`, `\e`, `\v` はまだ無いようです。一方で Version 6 Unix から AT&T の外に分岐した BSD では Version 7 Unix の仕様を引き継ぎ `-n` オプションを採用します。その結果 `\c` を採用した System V 系の `echo` は System V echo と呼ばれ、`-n` を採用した `echo` は（BSD 発祥と勘違いされて？）BSD echo と呼ばれているようです（参照 [0001206: The echo that supports -n is Bell Labs Unix echo, not "BSD echo"](https://www.austingroupbugs.net/view.php?id=1206)）。まとめると以下のようになります。

- 「改行しない」機能がない
  - Version 2 Unix (1972) 〜 Version 6 Unix (1975) 
- 「改行しない」機能がある
  - PWB/Unix (1977) `\c`, `\n`, `\\`, `\0xx` 
      - System III (1981) `\b`, `\f`, `\r`, `\t` 追加 ⇒ 後の System V 系
  - Version 7 Unix (1979)`-n` ⇒ 後の BSD 系

**補足** Bourne シェルが誕生し、初めて搭載された UNIX が Version 7 Unix です。それまでシェルには Thompson シェルや PWB シェルが使われており、この時点では `echo` はシェルとは完全に独立した別のプログラムでした。

### `-e` オプションの誕生と `-n` の普及

`-e` オプションは `echo` でエスケープシーケンスを解釈するためのオプションとして知られています。シェルの誕生年などから考えて、一番はじめに `-e` オプションを実装したのは Version 8 Unix (1985) のようです。[Version 7 Unix の echo](https://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/echo.c) には `-n` オプションしかありませんが、[Version 8 Unix の echo](https://minnie.tuhs.org/cgi-bin/utree.pl?file=V8/usr/src/cmd/echo.c) には `-e` オプションが追加されています。ただし反対の意味を持つ `-E` オプションはありません。

BSD ではそれまでの Bourne シェルからライセンス上の理由で ash (Almquist Shell) への置き換えが行われます。ash では文書化されていないながらも、オリジナルの1989 年版から `-e` オプションが実装されていました（[参考](https://www.in-ulm.de/~mascheck/various/ash/#original)）。GNU bash が誕生したのは 1989 年頃ですが、[Bash 0.99 fixes & improvements](https://groups.google.com/g/gnu.bash.bug/c/jgIKFVCQWAc/m/OETU9Yi2xx8J?hl=en) にて Version 9 Unix (1986) の動作に変更していくべきだという話がなされています。[Version 9 Unix の echo](https://minnie.tuhs.org/cgi-bin/utree.pl?file=V9/cmd/echo.c) のコードは Version 8 Unix のものと変更はないようです。ちなみに bash 1.05 (1990) には `-E` オプションはありませんでしたが、（ビルドオプション次第で？）[bash 1.11 (1992) には `-E` オプションがある](http://www.oldlinux.org/Linux.old/bin/old/bash-1.11/builtins/echo.def)ようです（補足 [古い bash の情報](https://unix.stackexchange.com/questions/243375/is-there-a-way-to-download-the-presumably-initial-bash-source-bash-0-99)へのリンク）

さて `-n` をサポートしたいわゆる BSD echo の系統が `-e` オプションによってエスケープシーケンスに対応していく一方で、エスケープシーケンスに対応した System V 系 Unix （の一部）の `echo` は `-n` オプションに対応していきます。いつから対応しだしたのかはまちまちで詳しく調べてみないとわかりませんが [echo(1) and printf(1)](https://www.in-ulm.de/~mascheck/various/echo+printf/) にある程度の情報がまとまっています。この流れによって System V echo と BSD echo はそれぞれ以下のように拡張されていきます。

- System V echo をルーツとするもの
  - 一部の実装で `-n` オプションが追加
  - `-e` オプションなし
  - デフォルトでエスケープシーケンスを解釈
- BSD echo をルーツするもの
  - `-n` オプションあり
  - 一部の実装で `-e` オプションが追加
  - `-e` を指定したときのみエスケープシーケンスを解釈

### シェルによる分岐

System V R1 (1983) までは、`echo` はシェルに組み込まれておらず外部コマンド（`/bin/echo` または `/usr/bin/echo`）を呼び出していました。System V R2 (1984) からシェルに組み込まれ、その頃より誕生する Bourne シェルの代替シェルの ash (1989)、bash (1989)、ksh88 (1988)、zsh (1990) などでもシェルに組み込まれました。これは OS が同じでも使用しているシェルによって `echo` コマンドの挙動が異なるということを意味しています。ちなみに `echo` がシェルに組み込まれた一番の理由はパフォーマンス改善のため（組み込みと外部コマンドでは数百倍の差がある）だと思いますが、OS 標準の `/bin/echo` または `/usr/bin/echo` は環境依存するため、シェルに組み込むことでどの環境でも同じ動きを実現できるようにしたいという考えもあったのではないかと推測しています。

シェルに組み込まれた `echo` の実装にもそれぞれに特徴があります。AT&T で Bourne シェルの後継とされていた ksh88 は `-n` オプションを実装せずデフォルトでエスケープシーケンスを解釈する道を選びました。ksh88 は System V で使われることを想定していたと思われるので System V echo の動作を踏まえているのでしょう。これに伴い ksh88 との互換性が目標であった pdksh （後継の mksh）や zsh でも同じようにデフォルトでエスケープシーケンスを解釈します。pdksh、mksh、zsh では `-e` オプションも実装されていますが、デフォルトで有効で `-E` オプションを指定することで無効にします。なお ksh93 では ksh93r (2005) 以降に `-e` オプションが実装されます。

ash 系は Version 7 Unix の Bourne シェルの代替として BSD で使用するために開発が始まったからだと思いますが `-n` オプションをサポートしました。また bash と同じく `-e` をサポートしており `-e` を指定した場合のみエスケープシーケンスを解釈します。ash の後継である FreeBSD sh や NetBSD sh でも同様です。しかし dash (debian 版 ash) では `-e` オプションは削除され常にエスケープシーケンスを解釈するように変更されました。最小の POSIX シェルの実装に留めることを目標としていた dash は、POSIX で規定されていなかった `-e` オプションのサポートをしたくなかったのではないかと考えています。dash の動作は `-n` を取り入れ始めた新しい System V 系の `echo` と同様の実装になっています。

文章ではまとまらなくなってきたので表にすると以下のようになります。太字は今も考慮しなければならないと思われる環境です。見て分かる通り考えられるパターンが網羅的に使われており、忘れていいのは、おそらく Version 6 Unix ぐらいでしょう。

| オプション      | エスケープ<br>シーケンス | シェル | ECHO_STYLE |
| ------------- | ----------------- | ------ | ------ |
| None          | No                | Version 6 Unix | `RAW` |
| None          | Yes               | System III, **本来の System V**,<br>ksh88, ksh93 on System V, **macOS sh** | `SYSV` (`XSI`) |
| `-n`          | No                | Version 7 Unix, **BSD 系**, ksh93(?), zsh 1.0.0 | `BSD` |
| `-n`          | Yes               | **修正された System V 系**,<br>ksh93q, **dash**, zsh 2.0.0 | `DASH` |
| `-n`<br>`-e`    | No (default)      | Version 8 Unix, bash 1.05, **ksh93r 以降**,<br>**FreeBSD sh**, **NetBSD sh** |
| `-n`<br>`-e`, `-E` | No (default)      | **GNU**, **bash 1.11 以降**, **BusyBox ash** | `GNU` |
| `-n`<br>`-e`, `-E` | Yes (default)     | **zsh 3.0.0 以降**, **mksh**, **OpenBSD sh** | `ZSH` |

上記の [ECHO_STYLE 変数](https://yash.osdn.jp/doc/ja/_echo.html#echo_style) とは yash の機能で、yash では指定した値によって各 `echo` の実装に動作を切り替えることができます。大まかな分類を把握するのに便利なのでメモとして書いています。なお yash のデフォルトは空 (`SYSV`) です。ただしデフォルトの `.yashrc` を使用するインタラクティブシェルでは `RAW` に設定されるので、これを含めると上記すべてのパターンを今も考慮しなければなりません。

ちなみに yash の `ECHO_STYLE` 変数のように `echo` コマンドの挙動を変更する機能は他のシェルにも実装されており、bash では `shopt -s xpg_echo`、zsh では `setopt BSD_ECHO` で機能を有効にすることができます。ksh93 は実行される環境（[universe](https://github.com/att/ast/issues/793)）が SystemV (att) か BSD (ucb) かで挙動を変更します。互換性の問題から廃止された ksh2020 ではこの universe のコードが削除されたようですが（[参考](https://github.com/att/ast/issues/370)）、互換性を重視した最新の ksh93u+m では（おそらく） universe のコードは残っています。



ここでちょっとしたクイズです。さまざまな `echo` の実装で `-n` という文字列（末尾改行あり）を出力するにはどうしたら良いでしょうか？

<details>
<summary>答えを見るにはここをクリック</summary>

```sh
echo -n             # Version 6 Unix, macOS sh など
echo -n - && echo n # BSD 系 など
echo -n -n && echo  # 同上、同下
echo '-n\n\c'       # dash など
echo -e '-n\n\c'    # bash など
echo - -n           # zsh のみ
```

</details>

とても面倒ですね。ちなみに最後の zsh では `-` （注意 `--` ではありません）というこれ以降をオプションとして扱わないというオプションが実装されているため Version 6 Unix を除いた中では一番自然に書くことができます。こういった `echo` の移植性の問題を解決するために作られたのが、次の `printf` コマンドです。

## もう大丈夫！何故って！？printf が来た！！

ここまでの話でわかるように `echo` コマンドというのは、よく使わている割に移植性が低い困ったコマンドです。そこで生まれたのが `printf` コマンドです。`printf` コマンドは `echo` が持っている機能、改行の制御やエスケープシーケンスの解釈を行うことができる、オールマイティなコマンドです。

説明するまでもないと思いますが、`printf` を使って改行せずに出力、または改行して出力するには以下のように書きます。

```sh
# 改行しない場合
printf '%s' 'Hello'

# 改行する場合（書式にエスケープシーケンスが使える）
printf '%s\n' 'Hello'
```

POSIX によると `printf` コマンドは Version 9 Unix から来た（[参照](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html)）と書かれているのですが

> should use the printf utility derived from the Ninth Edition system.

調べてみると [Version 9 Unix (1986-09)](https://minnie.tuhs.org/cgi-bin/utree.pl?file=V9/cmd) にはなく [Version 10 Unix (1989) には printf (1986-07-30)](https://minnie.tuhs.org/cgi-bin/utree.pl?file=V10/cmd/printf.c) があり [4.3BSD Reno (1990)](https://minnie.tuhs.org/cgi-bin/utree.pl?file=4.3BSD-Reno/src/usr.bin/printf) にもあると言った感じです。[こちら](https://www.freebsd.org/cgi/man.cgi?query=printf&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE&arch=default&format=html) には 4.3BSD-Reno で登場したと書かれています。Version 9 / 10 Unix は内部的に使用されていた Unix であることを考えると、日付から Version 9 Unix には含まれていなかったものの、1986 年頃の Version 9 Unix で使われ始め、世の中に広く登場したのは 1990 年頃の 4.3BSD Reno ということではないかと思います。時代的には Bourne シェルの代替シェルが出揃ったあたりで、POSIX でシェルとコマンドが標準化されるよりも少し前です。

余談ですが、ksh では `printf` ではなく似たような機能を持つ `print` が発明されています。[ここ](https://www.in-ulm.de/~mascheck/various/shells/ksh_versions.html)からたどり着けるソースコードから ksh86 時点ですでに組み込まれているようです。おそらく `printf` と同時期に別の場所で、おそらく `printf` よりもわずかに早く誕生したのではないかと思っています。`print` は ksh88 / ksh93 に加え pdksh、mksh、zsh でも採用されたのですが、広くは普及はしませんでした。

`printf` は `echo` に比べれば移植性に関して致命的な問題はないのですが、こちらもまた各実装で完全に互換性があるわけではなく、一部罠もあるので注意してください。例えばエスケープシーケンスを解釈し `\c` にも対応しているのですが、書式部分の解釈と `%b` による解釈で違いがあります。

```sh
printf "\0101" # => BS (010) 1 （\ddd 形式、d は 1〜3 桁、ISO C 標準）
printf "%b" "\0101" # => A (101) （\0ddd 形式、d は 0〜3 桁）

# すべて
printf "%b" "FOO\cBAR" # => FOO

# dash, bash, ksh93 (Solaris 11)
printf "FOO\cBAR" # => FOO\cBAR

# ksh93 （バグくさい）
printf "FOO\cBAR" # => FOOAR (46 4f 4f 02 41 52)

# mksh, zsh, Solaris 11 (/usr/bin/printf)
printf "FOO\cBAR" # => FOO

# BSD 系
printf "FOO\cBAR" # => FOOcBAR
```

[POSIX printf](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html) をよく読むと書いてあるのですが、`%b` はおそらく `echo` のエスケープシーケンスを移植するために作られた書式です。

> The %b conversion specification is not part of the ISO C standard; it has been added here as a portable way to process \<backslash\>-escapes expanded in string operands as provided by the echo utility. See also the APPLICATION USAGE section of echo for ways to use printf as a replacement for all of the traditional versions of the echo utility.

`\ddd` 形式とは異なり `\0ddd` 形式は標準的ではありませんし、`printf` では本来必要ないはずの `\c` が使えることから、従来の `echo` で使っていたエスケープシーケンスをそのまま移行できるように用意されたのが `%b` であるように思えます。`%b` は便利な場合もあるので使うなとまでは言わないですが、`echo` との移植用ということを考えると、必要ない限り使用しないほうが良いだろう私は考えています。ちなみに `printf` の第一引数で `\c` を使った場合は解釈に違いがありますが、そもそも使う必要がありませんし、POSIX でも標準化されていません。

余談ですが `printf` は浮動小数点数をサポートする必要はないのですが、

> The a, A, e, E, f, F, g, and G conversion specifiers need not be supported.

拡張機能としてサポートすることが推奨されています。

> Implementations are encouraged to support the floating-point conversions as an extension.

この時の小数点の記号に罠があります。ロケールによっては `.`（ドット）ではなく `,`（カンマ）が小数点記号として使われるため、出力するときの記号が異なるだけではなく、引数として小数点数を解釈する時にエラーが発生することがあります。`echo` の互換性の問題を解決するために作られた `printf` ですが、これとて完全な互換性はありません。そういったことも POSIX 文書をしっかり読み込めば知ることができます。「POSIX で標準化されているのだから互換性があるのだろう」ではなく、どういう所に互換性がないかを確認するために POSIX 文書を読みます。本当に大変で面倒で嫌な話です。

## POSIX での標準化

さて、このような歴史を経て `echo` コマンドは細かい違いを持った様々な実装が誕生してしまいました。そこで登場するのが POSIX です。POSIX 自体は 1988 年に POSIX.1-1988 として誕生したのですが、こちらは POSIX API （C 言語インターフェース）についての標準規格であり、シェルやコマンドに関しては少し遅れて 1992 年の POSIX.2-1992 です。頭の POSIX.1 が POSIX API、POSIX.2 がシェルやコマンドの規格とする予定だったのですが、なんやかんやあって POSIX.1 にシェルやコマンドも組み込まれ、今は POSIX.2 シリーズはありません。

さまざまな互換性がない実装を POSIX は巧妙な文章でまとめ上げ標準規格を作り上げました。それがどれだけ大変な作業で、どれだけ考えられているのかが `echo` コマンドの仕様から読み解くことができます。

POSIX は今まで数回改定されており、大きなバージョンは以下の 3 つ（＋次期 Issue 8）で他に細かい修正バージョンが複数あります（細かい修正バージョンは多いので以下には書いていません）。それとは別に UNIX の標準規格である Single UNIX Specification (SUS) があり、POSIX.1-2001 に SUS が組み込まれて以降は同一内容の標準規格となりました。

| Year | POSIX         | Single UNIX Specification (SUS)                  |
| ---- | ------------- | ------------------------------------------------ |
| 1992 | POSIX.2-1992  |                                                  |
| 1995 |               | SUSv1 (UNIX 95) = [Issue 4, Version 2][Issue4]   |
| 1997 |               | SUSv2 (UNIX 98) = [Issue 5][Issue5]              |
| 2001 | POSIX.1-2001  | = SUSv3 (UNIX 03) = [Issue 6][Issue6]（2004年修正版）|
| 2008 | POSIX.1-2008  | = SUSv4 (UNIX V7) = [Issue 7][Issue7]（2018年修正版）|
| 2023 | 次期POSIX予定  | = Issue 8（2022 年の終わり頃に改定予定だった）       |

[Issue4]: https://pubs.opengroup.org/onlinepubs/009656399/toc.pdf
[Issue5]: https://pubs.opengroup.org/onlinepubs/7908799/xcu/echo.html
[Issue6]: https://pubs.opengroup.org/onlinepubs/009696899/utilities/echo.html
[Issue7]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html

POSIX.2-1992 のドキュメントは有料なので私は持っていません。POSIX.1-2001 以降は公開されていますが、これは正確には Single UNIX Specification のドキュメントで、そちらは SUSv1 (Issue 4) から公開されています。ということで Issue 4 から見ていくことにします。上記の表のリンクから実際のドキュメントの `echo` コマンドの定義を参照することができます。

### Issue 4, Issue 5 より

Issue 4 の `echo`（302 ページ）より、まずは補足的な話から始めます。Issue 5 の内容は Issue 4 とほとんど同じです。

```txt
NAME
  echo − write arguments to standard output
SYNOPSIS
  echo [string ...]
DESCRIPTION
  The echo utility will write its arguments to standard output, followed by a newline
  character. If there are no arguments, only the newline character will be written.
OPTIONS
　  ︙
  Implementations need not support any options.
```

SUS では `echo` にはオプションがありません。これは元々 System V 系には `-n` オプションが存在せず、エスケープシーケンスを解釈する仕様だからと考えられます。ただし「Implementations **need not** support any options.」となっておりサポートする必要はない = サポートしても良いという意味（あっています？）だと思われるので拡張機能として実装しても問題ないことが示唆されています。後ほど説明しますが `echo` にオプションがないのは最新の POSIX 標準規格でも同じだったりします。

次に `--` オプションの話です。一般的に `--` は「以降の引数をオプションとして解釈しない」というオプションとしてあらゆる POSIX コマンドがそれに従うことになっているのですが、`echo` は例外的に従わないことが明確にされています。

```txt
OPTIONS
  The echo utility will not recognise the -- argument in the manner specified
  by Guideline 10 of the XBD specification, Section 10.2,
  Utility Syntax Guidelines; -- will be recognised as a string operand.
```

`--` オプションについては POSIX では [12.2 Utility Syntax Guidelines](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html) で標準化されており、POSIX コマンドにかぎらず一般的に広く使われています。

> Guideline 10:
The first -- argument that is not an option-argument should be accepted as a delimiter indicating the end of options. Any following arguments should be treated as operands, even if they begin with the '-' character.

しかしながら、比較的あとになってから普及したオプションのはずで、Version 7 Unix などでは使われていません。いつ頃からどこで誕生した仕様なのか把握してないのですが、古い時代には `--` への対応漏れがあるコマンドなどもあったようです（私もいくつか確認しています）。通常 `-` で始まるものはオプションとして扱われますが、`echo` コマンドでは歴史的にオプションとしては扱われておらず、不明なオプションはエラーにならずにそのまま出力されるのが仕様でした。Version 6 Unix では`--` はただの文字列としてそのまま出力され、Version 7 Unix やそれらから派生したさまざまな実装でも同様に文字列として出力されました。`echo` は（おそらく）すべての実装で「`--` はただの文字列として出力される」として同じ動きをしていたわけで、これを POSIX のユーティリティガイドラインに合わるためだけに別の意味に変更するわけにはいきません。変更してしまうとそれまでのシェルスクリプトが壊れてしまうからです。だから壊れないようにと例外的に `echo` では `--` はただの文字列として扱うように標準化されました。（POSIX がこうしなさいと決めたわけではないということです）

上の方で「`echo` を使って `-n` という文字を出力するにはどうすればよいか？」というクイズを出しましたが、もし `echo` が `--` というオプションに対応していたのであれば、`echo -- -n` と書くことができます。他のコマンドとも一貫性がある理想的な仕様なのですが「歴史的実装」とは異なるため POSIX はその仕様を採用することはできませんでした。ここで少し困ったシェルがあります。zsh です。zsh は当初 `--` をサポートすることによって `echo` で `-n` の出力が簡単にできないという問題を解決しました。しかし POSIX の規定とは明らかに反するため、おそらくこれを理由として `--` から `-` に変更したのでしょう（もしくは `print` コマンドにも `-` があるため単にそれに合わせただけかもしれません）。もっとも `-` も POSIX の規定ではそのまま出力しなければならないため、この点に関して zsh は POSIX に準拠できていません。

```
- neither of those could output arbitrary data as there was no
  end-of-option marker. That was fixed by the zsh echo
  implementation in 1990 (initially as -- then as -). echo -E -
  "$text" in zsh now works like in our V6 echo "$text" above.
```

いつ頃 `--` から `-` に変更されたのか気になったので、[zsh 1.0 のソースコード](https://github.com/llua/zsh-1.0/blob/master/builtin.c) を見てみたのですが、どうやら 1.0 では `--` だったようです。2.x（[2.0.0](https://github.com/llua/zsh-2.0.0), [2.1.0](https://github.com/llua/zsh-2.1.0)、[2.2.0](https://github.com/llua/zsh-2.2.0)、[2.3.1](https://github.com/llua/zsh-2.3.1), [2.5.0](https://github.com/llua/zsh-2.5.0)） のソースコードもざっと見たのですがどこで処理を行っているのかよくわかりませんでした。実際の挙動から 3.0.8 (2000) には修正済みで 2.0.0 (1991) から 2.5.0 (1994) のどこかで変更されたのだと思います。

さて本題の `-n` およびエスケープシーケンスの話です。少し説明しやすく整形しています。

```txt
OPERANDS
    The following operands are supported:
    string A string to be written to standard output. 
EX  If any operand is "−n", it will be treated as a string, 
    not an option. The following character sequences will be
    recognised within any of the arguments:
        \a Write an alert character
          ︙
```

まず引数 (OPERANDS) として複数の string（SYNOPSIS の [string...]）をサポートしていることがわかります。基本としてはこれだけで、あとは拡張機能 (EX) として定義されています。なお EX の定義は 30 ページに以下のように書かれています。

> EX The functionality described is an extension to the standards referenced above. Application writers may confidently make use of an extension as it will be supported on all XSI-conformant systems. These extensions are designed not to conflict with the published standards. If an entire SYNOPSIS section is shaded and marked with one EX, all the functionality described in that entry is an extension. Some behaviour which is allowed to be optional in the formal standards is mandated on XSIconformant systems. Such behaviours (for example, those dependent on the availability of job control) may not be individually marked as extensions, but the mandatory nature of the feature is marked as an extension where the option is described, typically in the header file where the corresponding symbolic constant is defined.

XSI 準拠のシステムでは必須となるオプション機能です。おそらく UNIX の認証を受けるためのものだと思っているのですが、少し自信がありません。まあ標準化のレベルに二段階あるということでしょう。拡張機能としては `-n` はオプションではなく文字列として扱わなければならず、エスケープシーケンス (`\a`, `\b`, `\c`, `\f`, `\n`, `\r`, `\t`, `\v`, `\\`, `\0num`) に対応しなければならないことが明確に書かれています。これは System V echo の仕様です。

Issue 5 の内容は基本的に Issue 4 と同じなのですが少しだけ変化があります。

> Implementations **will not** support any options.

オプションのサポートが need not から will not へと変化しています。これはオプションを「サポートする必要はない（がサポートしても良い）」から「サポートしてはならない」という意味に変わったと思っています（あっています？）。EX の部分がなくなっているのが気になりますが、他の場所の EX もなくなっているようなので、おそらくドキュメントの問題で消えてしまっているだけで変わっていないと思います。

### Issue 6, Issue 7 より

SUS が System V をメインとした標準規格なのに対して POSIX は System V と BSD の両方に対応した標準規格です。SUS が POSIX.1-2001 (Issue 6) にマージされて仕様はどう変わったでしょうか？ おさらいですが歴史的な実装では System V echo はエスケープシーケンスの解釈のみを行いました。BSD echo はその反対に `-n` オプションのみをサポートしました。したがって両方の動作を満たすように標準規格を策定する必要があります。[POSIX echo](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html) の下の方に「IEEE Std 1003.1-2001/Cor 1-2002, item XCU/TC1/D6/21 is applied, so that the echo utility can accommodate historical BSD behavior.」と書いてあることからもわかるように Issue 6 で BSD の歴史的な挙動に対応できるように修正された事がわかります。修正の差分 (XCU/TC1/D6/21) は「[What's New in Technical Corrigendum Number 1 for IEEE Std 1003.1-2001?](https://unix.org/version3/whatsnew-in-tc1.html)」経由でたどり着いた「[Single UNIX® Specification, Version 3 Technical Corrigendum No. 1](https://publications.opengroup.org/standards/corrigenda/u057)」で確認することができました。

なお Issue 6 と現在最新の Issue 7 は文言が微妙に異なるだけで内容は変わっていないようなので Issue 7 を参照します。

```
OPERANDS
  The following operands shall be supported:

  string
    A string to be written to standard output. If the first operand is -n,
    or if any of the operands contain a <backslash> character,
    the results are implementation-defined.
```

引数の string を標準出力に出力する機能は同じとして「**最初の引数が `-n` または 引数にバックスラッシュが含まれている場合は、その結果は実装定義**」という文が追加されています。つまりこういうことです。

- `echo -n ...` 
  - ⇒ 最初の引数が `-n` だからどう出力されるかわからない！
- `echo '\t' ...`
  - ⇒ 引数にバックスラッシュが含まれているからどう出力されるかわからない！

どう出力されるかわからないのはあくまで POSIX の話です。実装定義なので各シェルのドキュメントを見れば定義されているはずです。注意すべきは「`-n` は改行しないという意味」や「バックスラッシュはエスケープシーケンスとして解釈する」と**書いていない**という点です。POSIX は何も決めていません。実装定義というのは OS やシェルが仕様を定義しなければいけないことが要求されています。OS やシェルは仕様を定義していさえいれば、どのような実装であっても POSIX 違反にはなりません。つまりは自由に決めていいよと POSIX は言っているわけです。もっとも実装者は他の実装とある程度の互換性をもたせようとするため、自由に決めていいよと言われても、他の実装と全く異なる機能にすることはないでしょう。そしてシェルスクリプトを書く側は POSIX を参照してどの環境でも同じように動かないとわかるので、その対策を行わなければいけません。実装定義の機能を使わないようにする、対応シェルを限定する、場合分けをして各環境に対応する、などです。

ここで「POSIX が動作を統一してくれれば移植性の問題なんか気にしなくてすんだのになー」と思うかもしれません。確かにその通りなのですが、それは POSIX の役目ではありません。POSIX が `-n` を改行しない機能として実装しなければいけないと決めると、それまでの System V 用のシェルスクリプトが壊れ、エスケープシーケンスの解釈をしなければならないと決めると、それまでの BSD 用のシェルスクリプトが壊れるからです。それまでのシェルスクリプトを壊さないためには、`-n` オプションもエスケープシーケンスも対応してもしなくてもどっちでもいいよ（今までの実装のままでいいよ）とするしかないのです。

私も動作が統一されればいいなとは思います。もし Linux (GNU)、BSD、System V 各陣営が協力して「シェルやコマンドの仕様を統一するぞ！」と宣言して、それが実現されたらその時は POSIX は統一した標準規格に内容を変更するでしょう。しかし私はそんなことは今後もありえないと考えています。互換性のほうが重要ですし、POSIX の範囲だと基本的なテキスト処理やファイル管理やプロセス制御ぐらいしかできず、それだけを統一してもあまり役に立たちません。多くのものを作り変えなければ実用的に意味がなく、大変すぎる作業の割にメリットが小さすぎます。各 OS ベンダーに移植性問題の解決を期待するよりも、自分で POSIX コマンドを置き換えるどの環境でも動くコマンドセットを作るほうがはるかに簡単です。

さて次の段落です。

```sh
  string
      ︙
    [XSI] [Option Start] On XSI-conformant systems, if the first operand is -n, 
    it shall be treated as a string, not an option. The following character 
    sequences shall be recognized on XSI-conformant systems within any of the arguments:

      \a Write an <alert>.
        ︙
```

`[XSI]` というマークが付いていますが、これは（おそらく Issue 5 での `EX` 相当で）UNIX の認証を受けるのであれば、この仕様を満たさなければいけないというオプション規格です。POSIX に準拠するだけならこの仕様を満たす必要はありません。Issue 5 (SSUS) と Issue 7 (POSIX) の変更点を見てみましょう。

Issue 5 (SUS)

> If any operand is "−n", it will be treated as a string, not an option.

Issue 7 (POSIX)

> if the first operand is -n, it shall be treated as a string, not an option.

この二つの重要な違いは **any** operand  と **first** operand です。

実は POSIX では `-n` は何番目 (any) でも指定可能なオプションではなく「最初の引数」として扱われています。これはあくまで POSIX の定義の話であって bash などではオプションなので勘違いしないようにしてください。なぜ POSIX ではオプションとして扱っていないのかというと問題があるからです。以下の出力結果を見てください。

```console
$ # bash, ksh, mksh, zsh, Busybox ash, OpenBSD sh
$ echo -n -n foo
foo （改行なし）

$ # Linux (GNU 版)
$ /bin/echo -n -n foo
foo （改行なし）

$ # dash, FreeBSD sh、NetBSD sh
$ echo -n -n foo
-n foo （改行なし）

$ # FreeBSD (BSD 版), Version 7 Unix, OpenBSD
$ /bin/echo -n -n foo
-n foo （改行なし）
```

オプションであれば普通は複数指定することができるはずです。しかし上記の例より一部の実装では、二番目の引数として指定した場合 `-n` という文字列が出力されていることがわかります。傾向として比較的新しい実装はオプションとして実装してあるので複数指定できるのに対して、歴史的な実装では `-n` は最初の引数の場合しか効果がない感じです。現実にある実装がこのような挙動をしているので、POSIX では `-n` をオプションとして定義することができず、現実の実装の動きを反映させて「最初の引数」として扱うという形で標準化せざるを得なかったのです。`echo -n -n ...` という書き方は二番目の引数に `-n` オプションを指定している形ですが、この形は最初の引数は必ず `-n` オプションになります。ということは POSIX が定義している「最初の引数が `-n` だからどう出力されるかわからない！」を満たすので、どのような動作をしても POSIX に準拠した動作です。

さて上の方で書いた `ECHO_STYLE` の表を再掲します。古いシェルは省いています。以下の中で POSIX Issue 7 の標準規格に適合する `echo` の実装はどれでしょうか？

| オプション      | エスケープ<br>シーケンス | シェル | ECHO_STYLE |
| ------------- | ----------------- | ------ | ------ |
| None          | No                | Version 6 Unix | `RAW` |
| None          | Yes               | 本来の System V, ksh88, macOS | `SYSV` (`XSI`) |
| `-n`          | No                | BSD 系 | `BSD` |
| `-n`          | Yes               | 修正された System V 系, dash | `DASH` |
| `-n`, `-e`    | No (default)      | ksh93, FreeBSD sh, NetBSD sh |
| `-n`, `-e`, `-E` | No (default)      | GNU, bash, BusyBox ash | `GNU` |
| `-n`, `-e`, `-E` | Yes (default)     | zsh, mksh, OpenBSD sh | `ZSH` |

始め引数に `-n` がある or ない、エスケープシーケンスを解釈する or しない、の組み合わせの四パターン、つまり`RAW`、`SYSV` (`XSI`)、`BSD`、`DASH` のみです。bash を含む下三つの実装は POSIX に規定されていない `-e`、`-E` オプションが有るため POSIX に準拠していないということがわかります。

### Issue 8 で echo はこう変わる！

上記の表より現在の POSIX Issue 7 に準拠していない `echo` の実装は GNU / bash および、ksh93、mksh、zsh、FreeBSD sh、NetBSD sh、OpenBSD sh と複数あります。準拠していない理由は、一番目の引数に `-e`, `-E` を指定した時に "-e"、"-E" という文字列を出力しないからです。一番目の引数が `-n` の場合は実装定義でした。しかしそれ以外の場合はそのままの文字で出力するというのが標準化された `echo` の仕様です。

それが POSIX Issue 8 の改定で、これらの実装が POSIX に準拠するようになります。さてどのような改定が行われるのでしょうか？ 勘の良い方、ここまでこの記事を読んで POSIX 標準化委員会の考え方を理解した人ならすぐに分かるでしょう。

`-n` に加えて「最初の引数に `-e` や `-E` が指定されたときはその結果は実装定義である」に改定されるのです。🎉🎉🎉（実際の[変更内容](https://www.austingroupbugs.net/view.php?id=1222)）

> If the first operand consists of a '-' followed by one or more characters from the set { 'e', 'E', 'n' }, or if any of the operands contain a \<backslash\> character, the results are implementation-defined.

具体的には「`-` に続く、一つまたは複数の文字が `e`、`E`、`n` のいずれかの文字であれば実装定義」となっています。補足ですが FreeBSD sh と NetBSD sh は `-e` と `-n` の両方に対応していますが、両方を同時に指定することはできないようで `-en` または `-ne` だと文字列として扱われます。[Version 8 Unix の `echo`](https://minnie.tuhs.org/cgi-bin/utree.pl?file=V8/usr/src/cmd/echo.c) は `-en` および `-ne` にも対応していますが `-nn` のようなものには対応していません。いずれにしても「`-` に続く、一つまたは複数の文字が `e`、`E`、`n` のいずれかの文字であれば実装定義」という条件を満たしているので、どのような動作でも POSIX に準拠した動作です。

POSIX の方針を理解した人であれば、このように変更することに納得できるでしょう。しかしそうでない人は、今まできっちり決まっていた仕様が、実装定義という「決まっていない仕様」に変更することが、POSIX 標準規格の改善になると理解できないのではないかと思います。逆に bash が `-e` や `-E` と言った独自拡張を勝手に付け足したんだから「POSIX が決めた仕様を守って削除しろ！」と言いそうです。（すでに書いた通り、歴史的には `-e` という独自拡張？を付け足したのは bash ではなく Version 8 Unix で、 POSIX 誕生よりも前の 1985 年の話です）

そう、だから POSIX は仕様を決める団体ではないのです。あくまで現実の実装がどうなっているのかを文書化するのが仕事で、実際に `-e` や `-E` を指定した場合に移植性がないのだから、その現実を文書化して、それを読んだ私達に高い移植性を持ったアプリケーション（シェルスクリプト）を開発するためにはどうすればいいかというガイドラインを示す、つまり最初の引数に `-e` や `-E` を指定したときはどう動くかわからんよと、以前よりも正確に伝えたから POSIX 標準規格の改善なのです。

実装定義というのは「実装しろ」という意味でもありせん。`-e` や `-E` を実装しなくてもいいのです。つまり今まで通りで何も変えなくていいのです。なぜかというと実装されていない `echo` に `-e` や `-E` を実装してしまったら、今までのシェルスクリプトが壊れてしまう可能性があるからです。だから **POSIX の改定によって既存の `echo` の実装が変更されることはありません**。**`echo` の互換性問題は何も解決しませんが、bash（など）の `echo` は POSIX 準拠になる**のです。屁理屈のように感じるかもしれませんが、このように現実の実装を説明しているだけだから POSIX が長い間 Unix / Linux の標準規格として採用され続けているのです。

ちなみに `echo` に関しては、実装者に何かしろと要求しているわけではありませんが、実装しろと要求することもあります。それは新機能の追加の場合です。新機能を追加する分には既存のシェルスクリプトが壊れる可能性はほとんどありません。だから移植性を高めるために OS ベンダーに実装してくれと要求することがあります。ただし POSIX が新機能を発明することは基本的にありません。POSIX にとっての新機能というのはすでに世の中に誰かが作っているもの中から普及したものを選び出して標準規格に取り入れることです。POSIX の改定で追加されれる新機能というのは、私達にとってすでに馴染み深いものです。

こういった考え方は POSIX 文書をよく読んだり、[Austin Group Defect Tracker](https://austingroupbugs.net/main_page.php) やメーリングリストの議論の流れを見ていくと理解できるようになります。例えば改定後の RATIONALE（理論的根拠）には以下の文章が追加されます。

> This standard now treats a first operand of -e or -E the same as -n in recognition that support for them has become more widespread in non-XSI implementations. Where supported, -e enables processing of escape sequences in the remaining operands (in situations where it is disabled by default), and -E disables it (in situations where it is enabled by default). A first operand containing a combination of these three letters, in the same manner as option grouping, also results in implementation-defined behavior.

改定した理由は「`-e` や `-E` をサポートした実装が普及したから」と明らかに書かれています。一方で zsh の `echo -` の動作に関しては普及してないからという理由で `-` を出力するという動作を維持したいと考えていると書かれています（[参照](https://austingroupbugs.net/view.php?id=1222#c4373)）。ちなみに RATIONALE には `-e` や `-E` の意味が書かれているのに、本文中には定義せず、実装定義としか書いていないのも面白い点です。具体的な仕様をあえて決めないことで、将来的にもっと優れた独自機能を誰かが発明できる余地（拡張性）を与えているのです。

## GNU echo には POSIX に準拠してない点がある？

GNU bash と GNU echo はどちらも GNU のソフトウェアですが仕様が完全に同じというわけではありません。GNU echo (`/bin/echo`) は `--version` や `--help` と言ったオプションをサポートしており以下のように出力されます。

```console
$ /bin/echo --version
echo (GNU coreutils) 8.30
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

作者 Brian Fox および Chet Ramey。
```

POSIX では最初の引数が `-n` （Issue 8 では `-` に続く `n`, `e`, `E` のいずれかの文字の一つ以上の並び）の場合を除いてそのまま文字列としてて出力されることになっているので、`--version` や `--help` オプションは POSIX に準拠していないということになります。だからといって文句を言ってはいけません。これはわかっていてやっていることであり、環境変数 `POSIXLY_CORRECT` を設定すると POSIX 準拠の動作になるからです。

`POSIXLY_CORRECT` というのは、実装した後で「あ、POSIX に準拠できてなかかった！でも動きを変えると互換性に問題が出るし、仕方ないからこの変数を使ってなおそう。あーあ、ミスったなぁ。」と**いうものではありません**。GNU は POSIX などの他の標準規格を命令ではなく提案として扱うことを明確に述べています（[4.1 Non-GNU Standards](https://www.gnu.org/prep/standards/html_node/Non_002dGNU-Standards.html)）。リンク先のページを読むとわかるように「POSIX で必要とされる馬鹿げた動作が必要な場合」に有効にするもので、POSIX に準拠していると主張するためだけに用意されたものです。ようは「GNU は POSIX に準拠してないじゃないか！」と文句を言う人を黙らせるためだけのものです。`POSIXLY_CORRECT=1` すれば `/bin/echo --version` は `--version` という文字を出力するので、GNU echo は POSIX に準拠しています。

補足ですが、GNU echo は `POSIXLY_CORRECT=1` の状態で以前から POSIX には準拠していましたが、XSI 拡張には準拠しておらずエスケープシーケンスを解釈しませんでした。この問題は GNU Coreutils 8.31 で修正されエスケープシーケンスを解釈するようになりました（[echo: always process escapes when POSIXLY_CORRECT is set](https://git.savannah.gnu.org/cgit/coreutils.git/commit/src/echo.c?id=8d328706c6e)）。ただし XSI の要求とは異なり `-n` は文字列として出力されません。[経緯](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32703)を見るとどうやら dash の挙動に合わせたようです。

このように POSIX があったからと言って各実装が素直にそれに従っているとは限らず、GNU のように合理的理由があれば POSIX を採用しないと明言しており、また `POSIXLY_CORRECT` のようなオプションを付けていたとしても仕様が変更になることがあるわけで、POSIX に準拠してアプリケーションを書けばどこでも動くようになるというような気楽なものではありません。あくまで移植性を高めるためのガイドラインであり、私は POSIX を参照して現実の実装で問題になりそうな罠を把握し、実際の環境でテストして動作を保証しています。POSIX に準拠しているという理由で動作保証はできません。

## `echo`/`printf` のエスケープシーケンス対応は間違いだった

さて再び歴史をさかのぼります。Version 6 Unix までの `echo` はオプションもエスケープシーケンスも対応していませんでした。Version 7 Unix と BSD 系の `/bin/echo` は `-n` オプションのみに対応しており、エスケープシーケンスを解釈しませんでした。実は `echo` の機能としてはこれだけで必要十分で、エスケープシーケンスへの対応は蛇足（不要）だったという話をしたいと思います。

`printf` コマンドの書式は `%s` などのパーセントを使った表記（引数を当てはめる機能）と `\n` のようなバックスラッシュを使う表記に対応していますが、そもそもこの二つは全く別の機能です。`printf` に本当に必要なのはパーセントを使った書式のみです。

本当に必要だったものは `echo` / `printf` がエスケープシーケンスに対応することではなく、シェル自身がエスケープシーケンスに対応することです。[こちら](https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo)を読むと、このことに気づいたのは ksh の開発者である David Korn らしいですが、この話の出どころまでは調べていません。それはともかく David Korn が ksh93 で追加した `$'...'` (Dollar-Single-Quotes) こそが本当に必要だったものです。

例えば変数にタブ文字を代入したい時、POSIX Issue 7 の範囲では `TAB=$(printf "\t")` のように書きます。これが `$'...'` を使用すると `TAB=$'\t'` のように書くことができます。エスケープシーケンスを解釈するのは誰かというと`echo` や `printf` はコマンド（元々はシェルとは独立した外部コマンド）ですが、`$'...'` はシェルの文法でありシェル自身が解釈します。シェルの文法であるため文字が使えるところであれば、変数への代入など自由に使うことができます。短く書くことができコマンド置換を使うよりパフォーマンスがいいというメリットもあります。

`$'...'` があれば `echo $'FOO\nBAR'` や `echo -n $'FOO\nBAR'` や `printf $'%s\n'` のように書くことができ、`echo` や `printf` 自身にエスケープシーケンスの解釈の機能を持たせる必要はありませんでした。もっと早くに `$'...'` が発明されて普及していれば、もっと言えば Bourne シェルの時点でダブルクォートを使った時にエスケープシーケンスを解釈するような仕様だったら、`echo` に移植性の問題は発生せず、`printf` も小さくできたのではないかと思います。まあ当時の世界ではシェルを小さくし、`echo` に機能を持たせるほうが良いという判断だったのでしょう。`echo` がエスケープシーケンスに対応した時はまだ Bourne シェルは誕生しておらず、もっと原始的な Thompson シェルや PWB シェルの時代でした。

さてこの `$'...'` ですが、こちらも POSIX Issue 8 で標準化されることが予定されています（[0000249: Add standard support for $'...' in shell](https://www.austingroupbugs.net/view.php?id=249)）🎉🎉🎉。ただし dash など現時点でサポートしてないシェルが複数あるので注意してください。dash はメンテナンスがアクティブですし、オープンで誰でもパッチを提供できるので近いうちに対応するでしょう。POSIX で標準化されている機能には対応する方針なので拡張機能に対して拒否する理由はないはずです。OSS 系は誰かがやってくれるはずと思っているので心配はしていませんが、気になるのは商用 UNIX の方です。`$'...'` に関しては ksh93 を使っているはずなので、すでに対応していますが、それ以外の変更に対して最新の POSIX に対応してくるのかどうか。[UNIX 認証](https://www.opengroup.org/openbrand/register/) を受けてる UNIX を見ると AIX は最新の UNIX V7 に対応していますが、終息予定の HP-UX は 一つ前の UNIX 03 止まり、Apple も UNIX 03 どまり、Solaris は UNIX の認証受けるのやめてしまいましたしね。

## まとめ

`echo` コマンドの移植性問題の発端は、Unix での最初の実装のエスケープシーケンスへの対応と `\c` を使った改行の抑制というあまり優れているとは言えない仕様でした。それはすぐに修正されましたが System V 系と BSD 系として分裂してしまいました。その後 System V 系では `-n` オプションのサポート、BSD 系では `-e` によるエスケープシーケンスへの対応が、一部の実装で段階的に行われました。機能的には近づきましたが、デフォルトでエスケープシーケンスの解釈をするかどうかという違いが残り、`echo` コマンドは単純な機能の割に移植性に関して致命的な問題を抱えたコマンドとなってしまいました。

`echo` コマンドの問題を解決すべく新しく `printf` コマンドが開発されました。一部の人は `echo` コマンドを推奨されないものとして考えていますが、`echo` コマンドに移植性の問題があるというのを知ってから知らずか、現在も `echo` コマンドは広く使われています。POSIX もまた広く使われているという現実を踏まえ、`echo` コマンドを廃止することなく実装によって異なる機能を「実装定義にする」ことで標準化しました。これによって既存の実装およびシェルスクリプトを壊すことなく POSIX に準拠することが可能になっています。この流れからもわかるように **POSIX で標準化されてからも互換性問題がなくなったわけではない**事がわかると思います。POSIX には仕様が厳密に定義されていない部分がいくつもありますが、これは定義するのを忘れたわけではなく、それまでのアプリケーションが壊れないように意図的にやっていることです。理由があってやっていることなので、今後も仕様が一つに定まることはありません。

POSIX というのは「俺らが考えた OS の仕様を守れよ」というようなものではありません。OS の仕様が先にあり、後からやってきて、同じところと違うところをまとめたものです。POSIX は仕様の統一は目指していませんし新機能の発明も（基本的に）しません。代わりに各ベンダーが自由に独自機能を組み込めるように POSIX 標準規格が拡張機能の実装を妨げることになってしまわないように考慮して策定されています。したがって各ベンダーの独自機能が廃止されることはありませんし逆に追加されていきます。そうやって作られた独自機能の中から普及し移植性が十分であると判断され時に、新たに POSIX に組み込まれるという流れです。

その考え方を理解していれば、POSIX コマンドだけでやっていこうという考えや、GNU 拡張や BSD 拡張を毛嫌いすることが、POSIX の方針とは正反対であることに気づくはずです。本来は拡張はどんどんやって、お互いに機能を取り込んで、移植性をもたせて、それが POSIX に取り込まれて、POSIX コマンドが便利になっていくという流れなのです。POSIX コマンドに最低限の移植性しかないのは、OS ベンダーがお互いの機能を取り込もうとしないからです。実際の所 OSS 文化の誕生で、OS 以外のソフトウェアが多く増えたので OS ベンダーがわざわざコマンドを作る必要はなく、OS は小さく保つべきという考えから、OS の基本コマンドである POSIX コマンドの機能拡張は最小限に留めようという流れになってしまったように思えます。なので時代に合わせて私達も POSIX コマンドにこだわることをやめるべきなのです。

しかしながら、そうは言っても OS の基本コマンドはどこでも最初からインストールされていることが多く、手軽に使えるというのは事実です。標準規格が改善されるとそういったものも気軽に使えるようになっていくでしょう。Issue 8 での `echo` コマンドの改定では、現実の実装で普及した `-e`、`-E` オプションについて実装定義とすることで、既存のシェルが POSIX 違反にならないようにしましたが、このような POSIX 標準規格側の問題点の修正だけではなく、世の中に十分広まったとみなされる新しいコマンドの追加も行われています。もうすぐ機能が完全になったドラフト 3 が作成される予定なので、それらはまた別の機会に紹介できればと思います。（私が紹介できるのはシェルスクリプト関係だけですが）