シェルスクリプト版 pwd と外部コマンド版 /bin/pwd の仕様の違いからわかるPOSIXとOSベンダーの方針の違い

## はじめに

実は `pwd` コマンドはシェルビルトインコマンドと外部コマンドで仕様が異なります。オプションなしで `pwd` を実行した場合、**シェルビルトインコマンドでは `pwd -L`** と同じ動きをしますが、**外部コマンドでは `pwd -P`** と同じ動きをします。この記事ではこの件について（推測ですが）POSIX と OS ベンダーの方針の違いを解説したいと思います。

## 動作の確認

まず `pwd -L` と `pwd -P` の動きがどのように違うのかを確認します。

```sh
$ mkdir /tmp/pwd
$ cd /tmp/pwd

$ mkdir dir
$ ln -s dir link

$ cd link
$ pwd -L
/tmp/pwd/link
$ pwd -P
/tmp/pwd/dir # macOS では /private/tmp/pwd/dir
```

つまり、シンボリックリンク上のディレクトリにいる場合、`pwd -L` (Logical) ではシンボリックリンクのディレクトリ、`pwd -P` (Physical) では実際のディレクトリが出力されます。

次に、シェルビルトインコマンドと外部コマンドで、オプションを省略した時の出力を確認します。

```sh
$ pwd
/tmp/pwd/link

$ /bin/pwd
/tmp/pwd/dir # macOS では /tmp/pwd/link
```

Linux (GNU coreutils 8.30)、FreeBSD 13.0、NetBSD 9.0、OpenBSD 6.9、Solaris 10/11 など多くの環境では `/bin/pwd` は上記のように `pwd -P` の動作でした。唯一 macOS 11.6 だけが `pwd -L` の動作でした。

## POSIX の指定は -L

実は [POSIX pwd](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pwd.html) の指定ではオプションを省略した時のデフォルトは `pwd -L` の動作になると規定されています。

> If both -L and -P are specified, the last one shall apply. If neither -L nor -P is specified, the pwd utility shall behave as if -L had been specified.

したがって、標準に準拠した動作は `pwd -L` なのです。

### macOS の対応

macOS のコマンドは基本的に FreeBSD のコードがベースとなっているのですが、調べてみると shell_cmds-116 で `4207130` と `4701537` の二箇所の修正が入っていることがわかります。

- https://opensource.apple.com/source/shell_cmds/shell_cmds-116/pwd/pwd.c.auto.html
- https://cgit.freebsd.org/src/plain/bin/pwd/pwd.c

https://opensource.apple.com/ から確認した所 shell_cmds-116 は Mac OS X 10.5 で導入されているようです。そしてこの Mac のバージョンは初めて UNIX の認証を受けたバージョンです。理由がわかってきましたね。macOS は POSIX に準拠させるために独自のパッチを当てて `pwd -L` 相当に変更したと考えて間違いないでしょう。

### Linux (GNU) の対応

実は GNU coreutils の場合は環境変数 `POSIXLY_CORRECT` を定義することにより POSIX に準拠した動作、つまり `pwd -L` 相当の動作になります。この環境変数は POSIX の "ナンセンスな" 仕様に厳密に準拠させるための環境変数で、GNU が間違って実装しているとかではなく、意図的に POSIX に準拠させなかった証拠にもなっています。

なおこの話は「[シェルスクリプト実行環境をPOSIX準拠モードに変更して互換性を上げる方法](https://qiita.com/ko1nksm/items/6ebdcba7050da2f3c585)」でも簡単に取り上げています。

## 歴史的な動作は -P

歴史的な Unix では `pwd` コマンドはオプションは存在せず `-P` と同じ動作をしていたようです。また Solaris 10 の Bourne シェル（`/bin/sh`）のビルトインコマンドも、オプションは存在せず `-P` と同じ動作をします。オプション自体が存在しないので `-L` を指定したとしても `-P` と同じ動作をします。

どうやら `pwd` にオプションが追加され `-L` がデフォルトとなったのは ksh88 からのようです。ksh88 以降のすべての POSIX シェルは（おそらく）ksh88 と同じ仕様である `-L` で実装していたため、移植性があるとみなされて POSIX の仕様となったと考えられます。

## なぜ POSIX に準拠させないのか？

シェルビルトインコマンドはすでに POSIX に準拠して `-L` の動作をしています。外部コマンド版も `-L` に変更した方が混乱がなくて良さそうに思えます。なぜそうしないのでしょうか？このような疑問の答えは、たいてい互換性上の理由です。

まず外部コマンド版の `pwd` が呼び出されることはめったにありません。なぜならシェルにビルトインされているからです。これは POSIX シェルだけではなく Bourne シェルでも同じです。`/bin/pwd` のように絶対パスで呼び出すこともまずありません。

しかし、外部コマンド版の `pwd` が呼び出されることはあります。それは POSIX / Bourne シェル以外のシェルを使っている時、具体的な例として csh / tcsh を使っているときです。POSIX では ksh88 をきっかけに `pwd` のデフォルトは `-L` に変わっていったと思われますが、csh / tcsh の世界では常に `-P` でした。

csh / tcsh の世界においてデフォルト `pwd -P` だったものが、いきなり `pwd -L` に変わってしまったら互換性は保てません。だから OS ベンダーは外部コマンド版の `pwd` のデフォルトを `pwd -L` に変えなかったのでしょう。macOS が `pwd -L` 変更したのは Unix 風 OS としての歴史が浅く POSIX シェル以外の移植性は下がるものの、互換性の問題は低いと考えたからだと思われます。

都合がいいことに POSIX ではシェルは、POSIX シェルを使うことが前提となっています。そして全ての POSIX シェルには `pwd` コマンドはシェルビルトインとなっています。シェルビルトインの `pwd` さえ `pwd -L` の動作になっていれば POSIX に準拠していることになるのでしょう。UNIX 認証を受けた Solarsi 11 [^1] でさえ、外部コマンド版の `pwd` のデフォルトは `-P` であるため、UNIX 認証のレベルで問題にならないのだとと思われます。

## POSIX と OS ベンダーの考え方の違い

POSIX は Portable Operating System Interface の略からも分かる通り、移植性のための OS のインターフェースです。つまり、いろんな OS との移植性を高くするためのものです。どの OS でも共通となるインターフェースを定義することで例えば UNIX と BSD と Linux のどの OS でも動作するソフトウェアを作るためのガイドラインが POSIX です。

では、例えば Linux のバージョンが上がった時に、過去のソフトウェアが動くこと、それは「移植性」でしょうか？移植はしているわけではないので違います。これは「（後方）互換性」です。OS がアップデートした時にソフトウェアが動かなくなってしまえばユーザーが困るわけで、どの OS ベンダーも互換性を第一に考えています。その一つの例が `pwd` コマンドということです。POSIX では `pwd` コマンドのデフォルトは `-L` と規定しましたが、外部コマンドの `pwd` のデフォルトは `-P` のままにしました。これは OS ベンダーが互換性を重視した結果なわけです。

## 補足 `pwd -L` をデフォルトにするべきではないという提案

これは最終的に却下されていますが、実は `pwd -L` をデフォルトにするべきではないという提案が「[0001013: cd and pwd should not default to -L](https://www.austingroupbugs.net/view.php?id=1013)」にてなされています。Bourne シェルの動作（`-P`）の方が有用性が高く、デフォルトはシェルの実装者が選択できるようにすべきだという主張です。

カーネルが行うパス名の解釈は `-P` 相当であるため、`-P` の方がふさわしいという意見も出ていますが、この話は POSIX.2-1992でシェルとユーティリティが最初に標準化された時に詳細に検討された話であり、すでに長い間デフォルトが `-L` であると扱われており、それを実装依存にしてしまうと `-L` がデフォルトであるという前提で移植性があると認められていたソフトウェアが、移植性が無くなってしまうという理由で却下されています。

まあ、この流れは当然の流れでしょうね。ここで面白いのは、各 OS ベンダーに POSIX に準拠せよと `pwd -L` を押し付けようとしているのではなく POSIX の標準規格を変えようとしているというところです。POSIX の標準規格というのは絶対的な仕様ではなく、現実に合わないとみなされれば、それは POSIX の仕様側の問題であり、POSIX 標準規格の方を変えましょうというのが、POSIX に対する正しい考え方なのです。

## まとめ

シェルビルトインの `pwd` コマンドと、外部コマンドの `/bin/pwd` はオプションを指定しない時のデフォルトの挙動が異なります。これは POSIX 準拠の移植性のための `pwd -L` と互換性のための `pwd -P` の両方の要件を満たすためです。POSIX が目指しているのは「移植性」であり、OS ベンダーが目指しているのは「互換性」なのです。この二つは重複する部分もありますが厳密には異なるものです。

こういった小さい挙動の違いであっても、その理由を調べて POSIX とはなんなのか？どういう方針なのか？ということがわかってきてとても面白いですね。

[^1]: Solaris 11 は [UNIX の認証を受けた](https://news.mynavi.jp/article/20180829-685663/)ものの、更新する価値はないと考えたため今は [UNIX 認証の一覧](https://www.opengroup.org/openbrand/register/)には表示されません。