POSIXとのつきあいかた

コンピュータ関係の技術的なものや技術的でないもの(標準規格とかいったようなものの半分は技術仕様ですが、もう半分は業界政治で出来ていると言えるでしょう)との「つきあいかた」にはいろいろあります。

私のこれまでの経験などから、POSIXとのつきあいかた、といったようなことについて、いくつかの事例を示してみたいと思います。

Bashismとのたたかい

まぁ、Bashismとの戦いという点では、おまえらPOSIXを守ってくれ、と言いたい側ではあります。日常生活しているシェルはbashであるとはいえ、shebang が #! /bin/sh となっているのにFreeBSD/bin/sh (ash系)で盛大にコケるようなものが生態系として定着してしまうのはなんだかんだ言って脅威なわけで、過去にいくつかのプロダクトに向けてチケットを投げたりパッチを書いたこともあります。Bashismではないですがseqコマンドの代替をシェル関数として実装したりだとか。

とは言え、自分自身、FreeBSDの環境で動いてしまうものについては見逃しているだろうことも多く(シェル(シェルスクリプト)の構文では、微妙な所でashではエラーにならないが、bash含め一般にはエラーになる例、というのを引っ掛けていたこともある)、一応それなりにチェックしたい時には、FreeBSDのベースシステムの実装、GNUの実装、Plan9由来の実装(portsにいくつかある)、Heirloom Project(heirloomとは遺産のこと)がメンテしている伝統的Unix由来の実装(これもportsから入れられる)、それぐらいを確認手段にしています。

GNU AWK

と、言ったそばから逆のことを言うようですが、AWKの ** 演算子は非標準として有名ですけども、AWKは言語仕様上、言語処理系側で拡張されてないとなんだかんだで色々なことができませんから、必要であればGNU AWK依存だと割り切って書く、ということも必要でしょう。今の所自分がAWKで書いたプログラムにはありませんが、gawkを使いたくなりそうな点というとまずは多言語対応とかそのあたりでしょうか。

GNU Coding Standards

GNU/Linuxシステムでよく使われているcoreutilsのユーティリティには、POSIXLY_CORRECTという環境変数を設定することによって、(おそらく各ツールの製作者がrationalと考えたものである)通常の挙動から、POSIXにより厳格に従った挙動に挙動を変更するものがあります。

あるいはもっと一般に、GNU Coding Standards ( https://www.gnu.org/prep/standards/standards.html )の中には多くの場所で、POSIXへの言及があります(重複を除いてざっと二十数箇所ぐらい)。これはある意味で「GNUプロジェクトという立場からの『POSIXとのつきあいかた』」と言えるでしょう。

(以下余談。その「POSIX」という名前をsuggestしたのはリチャード・ストールマンさんだ、という話があります*1。また、GNUのコーディング規則は、すごく古いもの http://think-gnu-distribution.appspot.com/html/tga04.html#h.2 も比較して読むと、時代の変遷や「GNUのコアな所」が見えて面白い)

paxコマンド

アーカイブユーティリティのpaxコマンド( http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html )は、POSIXの性格を示すコマンドの一つだと思います。tarとcpioという乱立に乱立を重ねた状態にあったアーカイブユーティリティについて標準化するにあたり、既存のものを標準化するのは諦め、さらにもう一つの(願わくば、混乱の収拾に至る道となってほしい)種を播いた、と言えるでしょうか。その種は、混乱の収拾に向けて、育っていると言えるでしょうか。paxコマンド、使ってますか? ちなみに私は使って(使えて)いません。

dateコマンドのオプションの無さ

標準Cライブラリ(libc)にはいくつかの便利な関数がありますが、それをちょっと使いたい、といった時に、Cのコードを書いて試すのは少々ではありますが、手間です(Cインタプリタ、たとえばCINT*2などを使うことで少しは手間を減らせるかもしれませんが)。

そういった時、類似した機能が使えるコマンド、というのは結構便利です。たとえばちょっとした整数値の十六進表記を確認したい時には、

$ printf '%x\n' 12345
3039

といった感じです。

多機能なlibcの関数のひとつに、strftime( http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html )があります。そして、普通にドキュメントを読んだだけではいったいなんのことやらよくわからない、「 %G と %V 」「 %W と %u 」「 %U と %w 」という、あまりメジャーでないフォーマット文字列があります*3

以前、dateコマンドの機能を使ってあれこれと実例を試して確認したことがあるのですが( http://ksmakoto.hatenadiary.com/entry/2014/10/12/132048 )、実はこのような実験に必要な、任意の時刻を指定してその時刻の文字列表示を得るためのdateコマンドの機能は、POSIXでは全く決められていません( http://pubs.opengroup.org/onlinepubs/9699919799/utilities/date.html を見ればわかる通り -u オプションと、フォーマット文字列を指定する、という引数しか標準にはない)。後から指摘されたのですが、特に何かうまい代替手段も無さそうだったので、記事には注意書きを追加したのみです。

これはPOSIXを追求しなかった例、という話でした。

bcコマンドの曖昧なエラー対処

本題とは無関係ですが、私はMacr055*4というテキストマクロプロセッサを作っています。M4という標準的な(POSIXにもあります http://pubs.opengroup.org/onlinepubs/9699919799/utilities/m4.html )マクロプロセッサではevalという組込みマクロで四則演算などの算術式の計算ができるので、同様の機能を実装したのですが、自分で実装するのは面倒だったので、外部のbcコマンドを利用することにしました。

算術式の計算は、ベンチマークに利用するのに便利ですから、あまり重いのも嫌です。そんなわけでbcには、標準入力から入力を得て逐次その結果を標準出力に吐くモードがありますから、それを利用しました。

処理対象が数式なのですから、ゼロ除算のような意味的エラーは別としても、構文エラーに対してはエラーを検出したいものです。そして、bcがそのようなエラーを検出した時、そのメッセージが標準出力に出力されるのか、標準エラー出力に出力されるのか、実装によってまちまちになっています。手元で確認したところでは、FreeBSDのベースシステムに入っているbcはstdoutに、GNU bc( https://www.gnu.org/software/bc/ )はstderrに吐いてきます。

これは困った、ということでPOSIXで確認( http://pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html )したところ、この点に関して明示が意図的に存在しないような(としか思えない)書き方になっている、ということを見つけました。

上から順番に関係しそうなものを見てゆきます。まず STDOUT の節には、「正常の場合の出力」のことのみが書いてあります。次の STDERR の節では「shall be used only for diagnostic messages」とあります。ここで「diagnostic messages」とあるのは何か、ということになりそうです。

その後は、bcが処理できる言語の仕様が書かれた EXTENDED DESCRIPTION の節が長く続いた後、最後にわずかにエラーに触れた記述があります。

CONSEQUENCES OF ERRORS の節の1つ目の段落で、引数で指定された名前のファイルにアクセスできなければ「diagnostic message」をstderrに吐け、とあるので、前述の STDERR の節で出力されるとされているのは、こちらのエラーのみということになりそうです。

次の段落の記述が問題の、入力中のエラーについて触れている所ですが、「インタラクティブな起動中は should print an error message」とのみあります。それをstderrに吐くべきかstdoutに吐くべきか、を示すような情報はどこにもありませんし、「should」ですから、絶対にそうしなければならないわけではない、ということでもあります。また EXIT STATUS についても、0 になるのは「All input files were processed successfully.」とあって、標準入力からの入力についてはやはりボカしているような感じがあります。

unexpandコマンドに-tオプションを付けると-aを付けたような挙動が強制抱き合わせになる変な仕様

タブは行頭にだけ使い、expandコマンドとunexpandコマンドで展開したり戻したりすれば、タブ幅の変換は普通はちゃんとできるはずです...が、実際には4タブだと解釈してハードタブ化しようと unexpand -t 4 とすると、なぜか -a オプションを付けていなくても、付けられているかのように動作しろ、と POSIX にはあります( http://pubs.opengroup.org/onlinepubs/9699919799/utilities/unexpand.html )。POSIX内で回避する手段は無さそうだったので、GNU版を使って非標準のオプション --first-only で回避しました(この項、思い出したので追記)。

まとめ

そういったわけで、他には、カーネルとのAPIに関してはLKML周辺の人々をウォッチしていれば、やはり時々、「POSIXとのつきあいかた」が見られるような話題を見ることができるでしょう。POSIXは、明確な参照実装が存在したりするような「仕様」とは、違うタイプの「標準」ではないか、と私は捉えています、というのが本稿の結論めいたもの、となるでしょうか。日本語と英語がうまく対応しないのですが、「標準」「規格」「仕様」、「standard」「specification」、それぞれ微妙にニュアンスが違いますし、現実に存在しているものは、たとえば似たように「標準」と銘打っていても、それぞれ違った位置づけだったりするものです。