LoginSignup
12
7

More than 1 year has passed since last update.

「え?UNIXなのにPOSIXに準拠してないの!?」シェルスクリプト実行環境をPOSIX準拠モードに変更して互換性を上げる方法 (Solaris, AIX, HP-UX, Linux, macOS, BSD)

Last updated at Posted at 2021-10-17

はじめに

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 指定できる値は、legacyunix2003 だけのようです。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 はそのために作られた標準規格です。それを使わないなんてありえません。

12
7
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
12
7