はじめに
POSIX の認証 または Unix の認証 を取得していれば POSIX に準拠している。そう思ってはいないでしょうか? たしかにそのとおりです。POSIX に準拠しています。ただしデフォルト状態で POSIX に準拠しているとは限りませんです。特に歴史が長い商用 Unix では過去にリリースしたバージョンとの互換性を重視して歴史的な Unix = System V 互換の動作である**「互換モード」がデフォルトになっていることが多く「POSIX 準拠モード」**にはなっていません。(注意 「互換モード」「POSIX 準拠モード」は私が便宜上つけた名前です。)
POSIX は移植性の問題を改善するために作られた標準規格なわけですから**「互換モード」で正しく動くソフトウェアを開発しているということは逆に POSIX 準拠の環境に移植したときに動かない可能性がある**ということです。POSIX 準拠のシステムを導入したとしても「POSIX 準拠モード」に変更していなければ宝の持ち腐れです。ハードウェアを新しくしてもシステム環境が 「互換モード」のままでは、なんのための POSIX なんだ?となってしまいます。
POSIX が最初に標準化されたのは 1988 年(シェルとユーティリティが標準化されたのは 1992 年)、もう 30 年も前の話です。それ以前の古いシステムはもう使われてないはずです。少なくとも UNIX の認証(UNIX 95 / 03 / V7)を取得しているシステムであれば POSIX に準拠しています。システムを更新するときに今までよりも古い OS を入れることはまずありませんし、今更 POSIX 以前のシステムまで考慮する必要があるとは思えません。将来の事を考えるのであれば移植先は POSIX 準拠の環境である可能性が高いわけで、POSIX 準拠モードにしておくべきでしょう。(今の時代に両対応で開発する意味があるとは思えませんが、もしどちらでも動くように作っていれば今すぐ「POSIX 準拠モード」を有効にしても動くはずです。そして古い環境を切り捨てれば無駄に両対応する開発コストも減らせます。)
注意事項
実を言いますと私は商用 Unix を業務で使ったことはありません。Solaris に関しては無料で試せるイメージが配布されていますのでテスト環境を用意していますが、それ以外の 商用 Unix はドキュメントを調べただけの情報となります。このような状態で私がまとめるべきなのか悩んだのですが teratail での質問を見て、実は商用 Unix を業務で使っている人でも「POSIX 準拠モード」をあまり知らないのではないのか?と思ったので無いよりはマシだろう、もし間違っていたとしても、何かしら情報が集まるだろうということで記事にしました。
UNIX の認証について
多くの商用 Unix が取得しているのは正確には POSIX の認証ではなく UNIX の認証です。UNIX の認証は POSIX 準拠に加え、POSIX ではオプション機能となっている XSI / UP オプション を必須にしたものです。それぞれの UNIX の認証と対応している標準規格を以下に記します。
- 1993: UNIX 93 (?) = XPG3
- POSIX ではなく SVID3 をベースとしたもの
- 1995: UNIX 95 (SUSv1) = XPG4v2 = Issue 4, Version 2
- XPG4v2 などを再構成したもので POSIX.1 と POSIX.2 がベース
- 1998: UNIX 98 (SUSv2) = XPG5 = Issue 5
- 2003: UNIX 03 (SUSv3) = XPG6 = Issue 6 = POSIX.1-2001 (最終改定版は 2004 Edition)
- 2008: UNIX V7 (SUSv4) = XPG7 = Issue 7 = POSIX.2-2008 (最終改定版は 2018 Edition)
2021 年現在、多くの商用 Unix (macOS を含む)が取得している UNIX の認証は、一つ前の UNIX 03 です。最新の UNIX V7 に対応しているのは AIX 7.2 以降ぐらいしかありません。少し古いですがこの記事の内容に影響はありません。
参考 POSIXとUnix関連の標準規格と歴史年表 (IEEE, SVID, XPG, SUS)
なぜデフォルトは POSIX 準拠モードではないのか?
なぜ UNIX の認証というものがありながらデフォルトは「互換モード」になっているのか?その理由は考えるまでもありません。POSIX が歴史的な Unix (System V) との互換性を切り捨てているからと言ってそう簡単に Unix ベンダーは System V 版を切り捨てられるわけがないからです。Unix ベンダーにとっては POSIX が登場するよりも前から使っているお客様の方が大事です。
もちろん POSIX もそういう事情を理解していないわけがありません。POSIX を押し付けた結果、Unix ベンダーが POSIX に準拠しなくなってしまっては POSIX 自身が困ります。そのためデフォルトが「互換モード」であってもよいし、仕様は最小限で各ベンダーが拡張機能を搭載することを妨げないものとなり POSIX に準拠しやすいようなゆるい条件となりました。そうした結果 Unix ベンダーは POSIX に準拠するようになり POSIX は成功した標準規格となりました。しかしその一方で Microsoft は Windows NT という Unix とは全く違う OSを半ば無理やり POSIX 準拠と認めさせることに成功したわけですがこれは別の話です。
POSIX 準拠モードにしないとどうなる?
まず「はじめに」で紹介した teratail での質問(awkコマンドを使用する同一のシェルスクリプトをSolarisとLinuxでそれぞれ実行したところ、異なる実行結果が返ってきてしまう)の話です。歴史的な awk は関数が使えなかったりと POSIX 準拠の awk に比べて大きく機能が劣ります。そして Solaris 11 ではデフォルト環境の awk は POSIX と互換性がない古い awk なのです。そのため質問者の移行元の Solaris 11 の環境と移行先の Linux 環境とで awk の挙動に違いが発生していました。また質問者はその他にも多くのシェルスクリプトを抱えており修正の必要があるか調査判断したいとのこと。もし仮に最初から Solaris 11 を POSIX 準拠モードに変更しその上でシェルスクリプトを動かしていれば、Solaris 11 から Linux へのマイグレーションも完璧ではないにしろ問題は少なかったはずです。
もう一つの例として tr
コマンドが有名だと思います。例えば文字列の中に含まれる数字を削除する場合の POSIX 準拠の方法は tr -d '0-9'
です。この動作は元々 BSD 系での動作です。歴史的な Unix(System V 系)では tr -d '[0-9]'
とカッコを書く必要があります。POSIX.2 仕様の策定の時(1992 年)に POSIX は System V 系の動作ではなく BSD 系の動作を採用しました。そのため POSIX に準拠した環境ではカッコは不要です。しかし POSIX 準拠モードの存在を知らなければ tr -d '[0-9]'
と書かないといけないと勘違いしてしまい古い書き方を続けてしまうでしょう。そして将来別の環境に移行する時に問題を引き起こしてしまいます。
すでに互換モードで開発してしまっている場合
スクリプトの数が少なければ修正はさほど大変ではないでしょうが、大量にスクリプトがあって一度にすべて対応するのが難しい場合は少しずつ修正していかなければならないでしょう。
そのためのシェルスクリプトプログラミングのテクニックはあるのですが、この余白はそれを書くには狭すぎます。ひとまず「こちら」でその一部を紹介していますので参考にしてください。
POSIX 準拠モードにする方法
Solaris
動作確認している環境は Solaris 10 と Solaris 11 です。Solaris では POSIX に準拠したコマンドが標準のパスと別の所に置かれています。
パス | 対応する標準規格 | 備考 |
---|---|---|
/usr/sbin:/usr/bin |
SVID3 / XPG3 | 非 POSIX |
/usr/xpg4/bin |
POSIX.2 / POSIX.2a / SUS / SUSv2 / XPG4 | |
/usr/xpg6/bin |
POSIX.1-2001 / SUSv3 |
上記のパスを環境変数に PATH
に設定することで Solaris の環境は POSIX 準拠モードとなります。実際にはこれらのパスを手動で設定するのではなく getconf PATH
を使用します。私の環境では以下のようなパスが返ってきました。
$ echo "$PATH" # デフォルト
/usr/sbin:/usr/bin
$ getconf PATH # on Solaris 10
$ /usr/xpg4/bin:/usr/ccs/bin:/usr/bin:/opt/SUNWspro/bin
$ getconf PATH # on Solaris 11
/usr/xpg6/bin:/usr/xpg4/bin:/usr/bin:/opt/developerstudio12.5/bin:/opt/solarisstudio12.4/bin
$ PATH="$(getconf PATH):$PATH" # 参考 設定方法例
補足 /usr/xpg6/bin
は Solaris 10 の環境に存在しているのですが getconf PATH
では取得できませんでした(理由は未調査)。また私は Solaris 10 からしか確認していませんが「autoconf のドキュメント」によると Solaris 2.0 (1992) の頃から /usr/xpg4/bin
はあったようなので POSIX.2 の標準規格が策定されたときから POSIX に準拠させることができていたということなのでしょう。
コマンド一覧
参考までにそれぞれにディレクトリにある POSIX 準拠のコマンド一覧です。これらのコマンドは System V 系となにかしらの違いがあると思われるコマンドです。もし POSIX 準拠モードを使っておらず、互換モードを前提として書かれたシェルスクリプトがある場合、これを他の環境(Linux)などに移行する時には、以下のコマンドの互換性に気をつける必要があるということです。
Solaris 10
$ ls /usr/xpg4/bin/
alias chgrp df fc grep ls nohup sort
unalias ar chown du fg hash m4 od
stty vedit at command ed fgrep id make
pr tail vi awk cp edit file ipcs
more read test view basename crontab egrep find
jobs mv rm tr wait batch ctags env
get kill nice sccs type who bg date
ex getconf link nl sed ulimit cd delta
expr getopts ln nm sh umask
$ ls /usr/xpg6/bin/
bc dc edit expr ls tr vi xargs
crontab ed ex getconf stty vedit view
Solaris 11
$ ls /usr/xpg4/bin/
alias chgrp du fgrep ipcs mv pr tr
ar chown ed file jobs nice read type
at command egrep find kill nl rm ulimit
awk cp env getconf link nm sed umask
basename crontab ex getopts ln nohup sh unalias
batch ctags expr grep ls od stty vi
bg date fc hash m4 patch tail wait
cd df fg id more pfsh test who
$ ls /usr/xpg6/bin/
bc dc ex getconf patch tr xargs
crontab ed expr ls stty vi
AIX
AIX ではいくつかの環境変数を使用します。
-
XPG_SUS_ENV
:ON
に設定すると POSIX 準拠となる -
XPG_UNIX98
:OFF
に設定すると UNIX03 準拠となる -
OCTAL_CONST
:ON
に設定すると Korn シェルの 8 進数 / 16 進数の定数の変換処理が POSIX 準拠となる(ksh88 だとecho $((010))
が 10 になってしまう件?)
参考一覧 | 対応する標準規格 |
---|---|
UNIX 98 | SUSv2 / POSIX.1 + POSIX.2 / Issue 5 |
UNIX 03 | SUSv3 / POSIX.1-2001 / Issue 6 |
UNIX V7 | SUSv4 / POSIX.1-2008 / Issue 7 |
参考
HP-UX
環境変数 UNIX_STD
を設定します。
-
UNIX95
: 設定すると UNIX 95 -
UNIX_STD
:95
または1995
を設定すると UNIX 95、2003
を設定すると UNIX 03
UNIX_STD=2003 # 参考 設定例
参考一覧 | 対応する標準規格 |
---|---|
UNIX 95 | SUSv1 / POSIX.1 + POSIX.2 / Issue 4 |
UNIX 03 | SUSv3 / POSIX.1-2001 / Issue 6 |
参考
Linux (GNU)
Linux では環境変数 POSIXLY_CORRECT
と _POSIX2_VERSION
を使用します。
まず POSIXLY_CORRECT
ですが、GNU は POSIX で標準化された後にソフトウェアの多くが完成したこともあり、基本的には POSIX に準拠しています。しかし "意図的に" POSIX に準拠させなかった所がいくつかあります。それは GNU はユーザーの利便性を一番に考えており POSIX を「絶対に守らないといけない決まり」ではなく「役に立つかもしれないというガイドライン」として扱っているからです。とは言えこれを根拠に「Linux は POSIX に準拠してない!」という攻撃をされても困るわけで、その回避策として(馬鹿げた) POSIX の標準規格に準拠するための環境変数が POSIXLY_CORRECT
です。このような経緯で作られたものであるため POSIXLY_CORRECT
を設定すると POSIX への準拠度は高まりますが、ユーザーの利便性は悪くなるので、 どうしても完全な POSIX 準拠が必要ない限り設定しなくて良いと思います。
もう一方の _POSIX2_VERSION
は名前からも分かる通り準拠する POSIX のバージョンを指定します。POSIX のバージョンによって細かい動作が異なるのでどの POSIX に準拠するのか?という指定です。指定できる値は以下のとおりです。
_POSIX2_VERSION | Standards |
---|---|
199209 |
POSIX 1003.2-1992 |
200112 |
POSIX 1003.1-2001 / UNIX 03 / SUSv3 / Issue 6 |
200809 |
POSIX 1003.1-2008 / UNIX V7 / SUSv4 / Issue 7 |
参考
POSIX 準拠モードで挙動が変わるコマンド
参考までに環境変数 POSIXLY_CORRECT
と _POSIX2_VERSION
によって挙動が変わるものをいくつか紹介します。
pwd
オプションなしで pwd
コマンドを呼び出した場合 POSIX pwd の仕様上は pwd -L
の動作になると規定されています。実際シェルビルトイン版の pwd
コマンドは pwd -L
として動作します。しかしながら外部コマンド版は pwd -P
として動作します。これはおそらく互換性のための仕様で、実は Linux 以外の環境でも外部コマンド版の pwd
は POSIX に準拠せず pwd -P
として動作します。この動作は環境変数 POSIXLY_CORRECT
を定義すると POSIX 準拠の動作をするようにかわります。
sort
こちら に簡潔にまとめてあります。翻訳するのが面倒なので日本語訳を引用します。
POSIX の新しいバージョンが、古いバージョンと非互換であることが、ときどきある。
たとえば、POSIX の古いバージョンでは、‘sort +1’ というコマンドは、 各入力行の二番目以後のフィールドに基づいて、行の並べ替えを行うことになっていた。
ところが、POSIX 1003.1-2001 では、同じコマンドが ‘+1’ という名前のファイルの行を並べ替えることになっており、 フィールドに基づいた並べ替えを行うには、‘sort -k 2’ という別のコマンドを使わなければならないのだ。
そして、さらにややこしいことに、POSIX 1003.1-2008 では、古い動作と新しい動作のどちらをする実装も認められている。
御使用のシステムが POSIX 1003.1-2001 に準拠しているのに、動かしているソフトウェアが ‘sort +1’ や ‘tail +10’ といった旧来の用法も持っている場合には、環境に ‘_POSIX2_VERSION=200809’ を設定することで、互換性の問題を回避することができる。
このように POSIX のバージョンによって挙動が異なる場合があるため、従う標準規格を厳密に指定するのが環境変数 _POSIX2_VERSION
です。
df
こちらやこちら(証明書の有効期限切れみたいなので一応アクセスは自己責任で)でリチャード・ストールマンが愚痴っているやつです。GNU 版の df
コマンドはデフォルトでは 1 KB 単位でブロック数を表示しますが POSIX では 512 バイト単位でブロック数を表示しなければいけません。環境変数 POSIXLY_CORRECT
を定義することで POSIX に準拠に動作が変わります。
$ df
Filesystem 1K-blocks Used Available Use% Mounted on
udev 15869788 0 15869788 0% /dev
tmpfs 3179864 3296 3176568 1% /run
:
$ POSIXLY_CORRECT= df
Filesystem 512B-blocks Used Available Use% Mounted on
udev 31739576 0 31739576 0% /dev
tmpfs 6359728 6576 6353152 1% /run
:
本当に一体誰が 512 バイト表示で嬉しいと思うのでしょうか?ちなみに macOS と OpenBSD は 512 バイト、FreeBSD と NetBSD は 1 KB 単位でブロック数が表示されました。
余談ですが、環境変数 POSIXLY_CORRECT
は 元々は POSIX_ME_HARDER
だったそうです。あるメンバーに説得されて変更したのですが、リチャード・ストールマンはこの変更を後悔しているそうです。POSIX 準拠が必ずしも正しい (CORRECT) わけではない、HARDER は困難なことに対する努力というニュアンスだということなのだと思います。
macOS
macOS では環境変数 COMMAND_MODE
で POSIX に準拠するかどうか、どのバージョンに準拠するかを指定します。macOS は Darwin の前身の NeXTSTEP を含めて POSIX 標準化以降に登場した OS であるため、POSIX 以前との互換性を保つ必要はありませんでした。そのためデフォルト状態で POSIX に準拠しています。環境変数 COMMAND_MODE
を設定するのはレガシーモードにするときです。
man 5 compat
(参考: compat(5))によると、環境変数 COMMAND_MODE
指定できる値は、legacy
と unix2003
だけのようです。unix2003
は UNIX 03 を意味します。(macOS 11.0 Big Sur は UNIX 03 で認証を受けています)
参考一覧 | 対応する標準規格 |
---|---|
UNIX 03 | SUSv3 / POSIX.1-2001 / Issue 6 |
ドキュメントによると legacy
にすると Mac OS X 10.3 の挙動に出来る限り近づけるようです。wikipedia の Darwin の情報によると Mac OS X 10.3 が登場したのは 2003 年なのでこれぐらいの時期に POSIX 準拠のための対応を行い、その前後で互換性に関する問題が大きくなったため環境変数 COMMAND_MODE
を追加したのでしょう。詳細には Mac OS X 10.3 (2003) で「FreeBSD 5に対応したBSDレイヤー」を搭載しており、Intel Mac の登場した Mac OS X 10.4 (2005) で「64ビットBSDレイヤー」を搭載し、Mac OS X v10.5 (2007) で完全な POSIX 準拠を実現した書かれています。
余談ですが、この頃に書かれたと思われる UNIX / Linux アプリケーションを OS X に移植するためのドキュメント Porting UNIX/Linux Applications to OS X には BSD のユーザー環境(コマンド)はインストールされない可能性があると書かれています。今もこの情報が当てはまるのかは不明ですが、当時は「Mac OS X 標準の BSD コマンド(シェル含む)」があることを想定すべきではなかったのかもしれません。この話は私がよく参考にするサイトの「~sven_mascheck/ - Various system shells」で知りました。(あまり関係ない話なのですが、どこかで書きたいと思ってブラウザのタブをずっと開きっぱなしだったのでようやくここで解放することができました)
An OS X user should never have to resort to the command line to perform any task in an application with a graphical user interface. This is especially important to remember since the BSD user environment may not even be installed on a user’s system. The libraries and kernel environment are of course there by default, but the tools may not be.
BSD
BSD 系すべてをちゃんと調べたわけではないのですが、FreeBSD に関して言えば「どの POSIX のバージョンに従うか?」を一括で指定するための環境変数は見つけられませんでした。だからといって FreeBSD が POSIX に完全準拠しているというわけではありません。FreeBSD ではどうやら一つの環境変数で指定するのではなく、個別の環境変数によって制御する方針だと思われます。
例えば Linux (GNU) の話でも紹介した df
のブロックサイズは FreeBSD でも POSIX に準拠しておらず 1 KB で表示されます。このブロックサイズは環境変数 BLOCKSIZE
で指定することができます。
$ df
Filesystem 1K-blocks Used Avail Capacity Mounted on
/dev/gpt/rootfs 4053308 3736036 -6992 100% /
devfs 1 1 0 100% /dev
$ BLOCKSIZE=512 df
Filesystem 512-blocks Used Avail Capacity Mounted on
/dev/gpt/rootfs 8106616 7472072 -13984 100% /
devfs 2 2 0 100% /dev
他にもコマンドごとにいろいろあると思うのですが、さすがに多すぎるので詳しく確認していません。ドキュメントの STANDARDS の項目を見ると POSIX に対する例外事項が書いてあるものが見つかります。それらを参照すれば POSIX 準拠にするための方法が分かるのではないかと思います。
さいごに
この記事ではシェアが多いと思われる OS しか書いていませんが POSIX に準拠しているシステムや、ほぼ準拠しているシステムは他にもあります。ですが少なくとも POSIX または UNIX の認証を受けているシステムであれば、デフォルト状態で POSIX に準拠していなかったとしても POSIX 準拠モードにする方法があるはずです。そうでなければ認証が通るはずがないからです。それらのシステムで何をすれば「POSIX 準拠モード」になるかはシステム次第なのでベンダーが提供しているマニュアルをよく確認すると良いでしょう。
ただし残念ながら POSIX は移植性・互換性の問題を完全に無くするようなものではありません。「What is POSIX? Richard Stallman explains」でリチャード・ストールマンは「POSIX に準拠しているシステム同士でも細かい違いがあり、その問題を解決するために GNU Autotools がある」ということを語っています。この記事で言及した autoconf はそのツールの一つです。autoconf のドキュメント「移植性のあるシェルプログラミング」には、シェルスクリプトに関する多数の移植性・互換性の問題が書かれています。(ただし古い System V や Bourneシェルことまで考慮した内容ですので POSIX 準拠を基準にすれば、ここに書かれた問題の多くは無視してできると思います。)
POSIX 準拠モードにしてもなお残る移植性・互換性の問題はシェルスクリプトのプログラミングテクニックを使うことである程度解決することができます。その方法は近いうちに記事にするつもりですが、まずは POSIX 準拠モードを有効にし可能な限り環境の違いを減らすことが重要です。POSIX はそのために作られた標準規格です。それを使わないなんてありえません。