LoginSignup
145
140

More than 1 year has passed since last update.

なぜシェルスクリプトはPOSIX準拠でも環境依存が激しいのか? 〜POSIXの問題点とその解決策の案〜

Last updated at Posted at 2021-06-15

まえがき

この記事は「シェルスクリプトで高い移植性と生産性を両立させるシリーズ」の第一弾です。移植性と生産性を両立させるための前提知識として POSIX コマンドの問題点について解説します。第二弾では高い移植性と互換性を実現させるための考え方、そして第三弾、第四弾ではそれを実現するシェルスクリプトの具体的な実装テクニックを紹介します。第五弾では現実的な問題と回避方法について解説する予定ですがまだ具体的な内容は決まっていません。第五弾はその前に「シェルスクリプト入門(仮)」の記事を書こうと思ってるので少し遅くなると思います。もし興味がある方は記事をストックしていると更新時に通知されると思います。

2021-07-11 追記 記事が長くなったので第三弾を追加しました。

関連記事

シェルスクリプトで高い移植性と生産性を両立させるシリーズ

タイトル
【第一弾】 なぜシェルスクリプトはPOSIXに準拠しても環境依存が激しいのか?
第二弾 高い移植性と生産性を両立するソフトウェアを書くのに必要な知識と考え方
第三弾 中〜大規模シェルスクリプトのためのメンテナンス性の高いディレクトリ構造
第四弾 シェルスクリプトの互換性と生産性の問題を解決する高度なプログラミング技術
第五弾 (タイトル未定)

はじめに

一般的にシェルスクリプトは環境依存が激しいといわれています。一方で移植性を高くするための POSIX という標準規格があるのだからそれに準拠してシェルスクリプトを書けば問題ないはずだと考える人もいます。確かに POSIX は移植性を高くするための標準規格ですがシェルスクリプトに関してはあまり役に立ちません。この記事では POSIX の正しい理解と問題点を解説し、どうすればそれを解決できるのか?という案(まだ十分に検証されていないもの)を提示します。

なおこの記事はシェルスクリプトを使うという前提の話であるため、シェルスクリプト以外を使って解決する方法はこの記事の対象外です。もちろん実際にはシェルスクリプト以外の好きな言語を使って解決して構いません。

POSIX コマンドを批判するあなたは誰なの?

まずはっきりさせておきたいのは、私がこの記事で批判する対象は POSIX すべてではなく POSIX シェルでもなく POSIX コマンド(POSIX で定義されているコマンド)です。具体的に言うと Utilities で定義されているコマンドです。これらのコマンドには POSIX シェルを含む awkc99fort77, sh といった別の言語を使うためのコマンドも規定されていますが、それら個別の言語については批判の対象ではありません。シェルの機能のように思われてる echo[ (test) コマンドは 「POSIX シェル」ではなく「Utilities」に含まれているものなので批判の対象です。(ちなみにシェルスクリプト関係を除く POSIX については知識がないので状況を全く知りません。)

私はシェルスクリプト用のテストフレームワークである ShellSpec の開発者です。ShellSpec は すべての POSIX シェル(bash だけでなく、dash、mksh, ksh、zsh 等 POSIX シェルに準拠しているシェルのすべて)に対応しており、POSIX シェルが動作するすべての環境(Linux、macOS、WSL、Cygwin、その他の UNIX)で動作します。他のシェルスクリプト用テストフレームワークと比較しても対応している環境が多く、それでいてもっとも多くの機能を備えています。

シェルスクリプト製のソフトウェアを開発している私が POSIX コマンドを批判するのはシェルスクリプトによる開発全体を改善したいからです。改善するためには現状を正しく理解し問題点を明らかにしなければいけません。シェルスクリプトは言語として好きですが、それとこれ(現状の問題)とは別の話です。

POSIX の標準化はシェルスクリプトの移植性問題の解決に(あまり)役に立っていない

Unix は 1969 年 AT&Tの ベル研究所で誕生しました。継続していく開発の中で BSD 系 と呼ばれる分岐が生まれました。本流は最終的に System V 系と呼ばれるようになりました。その Unix の開発の過程で多数のコマンドが生まれ Unix はより便利になっていきました。しかし互換性をそれほど重視していなかったためかコマンドを含む Unix は移植性が低下していきました。そこで生まれたのが POSIX です。1988 年にシステムインターフェースが、1992 年にシェルとコマンドが標準化されました。

しかしこの時、BSD 系、System V 系に存在していた多数の便利なコマンドは、移植性がないという理由でわずか 160 個(実際に使えるのは 100 個)程度しか標準化されませんでした。当初の POSIX には存在していた tar コマンドすら後の更新で削除されています。残ったコマンドも共通で使えるオプションのみが標準化されました。つまりコマンドの発展の歴史から見れば POSIX の登場によって大幅に便利さが後退してしまったということです。これは使いやすさを求めて発展してきた Unix (BSD 含む) と移植性のための POSIX とでは目的が違うからです。(少なくともコマンドに限って言えば)POSIX は移植性のために使いやすさを切り捨てたということです。

問題はそれだけではありません。BSD 系と System V 系で引数や入力に対して異なる動作をする場合やオプション省略時のデフォルトの動作、出力の細かい差異、そういったものはおそらくほとんど POSIX で統一されませんでした。例外の一つは tr コマンドです。範囲指定の書き方が System V 系([a-z])と BSD 系(a-z)で異なっていましたが、これは BSD 系の仕様を POSIX として採用しました。しかし多くのコマンドはこのような統一が行われず、代わりに「規定されない」「実装依存である」のように書くことで複数の実装を許容する形の仕様となりました。おそらく既存の実装を大きく変えることなく(移植性ではなく互換性重視で)POSIX に準拠できるようにしたかったのだと思いますが、こんな状況では POSIX に準拠したところでコマンドの移植性の問題は解決されません。

POSIX は今も改定が行われ続けていますが、やはりコマンドの動作の統一作業は行われていないようで、実装の挙動をベースに規格の内容は正しいか?本当に移植性があるのか?を検証しているだけに思えます。結局の所 POSIX では「これらのコマンドは動く可能性が高いが注意すべき点があるから気をつけよう」というガイドラインを作ったようなものです。もちろん不便なのは POSIX の標準規格の話であって、実際の実装は昔より便利になっています。POSIX で標準化されなかったからといって BSD 系、System V 系の環境からコマンドが削除されたわけではないですし、新たな GNU 系は更に便利になっています。単に POSIX で規定されたコマンドが不便なままなだけです。

現在の状況をウェブ開発の世界に例えてみると、今のシェルスクリプトの開発はブラウザ間の互換性が低かった IE6 以前の時代に jQuery なしでプログラミングするようなものです(もうそろそろレガシーブラウザ時代の開発を知らない方が増えてきたかと思うので、この説明がわかりやすいかといえば微妙ですが)。どのシェル(ブラウザ)でも共通で使える機能を学び、どの環境でも共通して使えるコマンドとオプションを見つけ出し、挙動が違うコマンドへの対応コードを書かなければいけません。ブラウザの世界では HTML5 策定時代に各ブラウザの挙動が同じになるように厳密に仕様が決められましたが、POSIX ではそのような厳密な仕様を決めることは行われませんでした。20 年前の生産性の悪いブラウザの世界が今のシェルスクリプトの世界です。

シェルやコマンドの違いを完全に把握して問題があるようなコードを書かないようにすればいい。それが技術力だと勘違いしている人もいますが、これはただのバッドノウハウです。本質的な生産性とは全く関係なくプログラマとしての技術向上には何の役にも立ちません。もし将来ブラウザのようにシェルやコマンドの違いがなくなってしまえばただの無駄知識となってしまいます。そういう知識をありがたがるのは「奥が深い症候群」です。

POSIX 全般の真実

前項で POSIX の問題点は大体書いていたりするんですが、より深く現状を理解するために POSIX とは本当は何なのか?という話をしたいと思います。なるべく正確に POSIX を理解しようとしたつもりですが、それでも「私の理解」であることには変わりないので注意して下さい。「それあなたの独自解釈だよね?」とか「そんなこと POSIX は言ってないよね?」と指摘されないように心がけたつもりですが、もし解釈が間違ってるんじゃないの?という所がありましたらご指摘いただけるとありがたいです。

POSIX のメインは C 言語用のインターフェースでありシェルスクリプトから使えるのはごく一部

ほとんどの人にとっては POSIX は C 言語用のインターフェースのことを指していると思うのですが、私のようにシェルスクリプトがきっかけで POSIX のドキュメントを読むようになった場合は、シェルスクリプトから見える世界が POSIX の世界の全てだと錯覚してしまう可能性があります。その戒めのためにこれを最初に持ってきました。

現在の POSIX の仕様は POSIX.1-2017 として一つにまとまっていますが、最初に標準化されたのは「POSIX.1: コアサービス(標準 ANSI C 含む)」で 4 年遅れで「POSIX.2: シェルとユーティリティー」が標準化されています。その後「POSIX.1b: リアルタイム拡張」と「POSIX.1c: スレッド拡張」が追加されています。

C 言語からは POSIX で定義された機能をすべて使うことが出来ますが、(POSIX で規定されている範囲の)**シェルスクリプトやコマンドからは POSIX の機能の一部しか使えません。**シェルスクリプトから直接使えない機能はファイルのバイナリ処理、ランダムアクセス、ネットワーク、リアルタイム処理、スレッド処理などがあります。

POSIX が Unix 系 OS が備えるべき最低限の仕様のことであるならば、POSIX 準拠のシェルスクリプトは Unix のすべての機能どころか最低限の機能すら使えないということになります。つまり POSIX 準拠のシェルスクリプトだけで何もかもやろうとする発想は根本的に間違っており、何かが実現できない、もしくは実現できても(シェルスクリプトに適した処理以外は)性能が低いということになります。(性能が低くても実用になれば問題ないじゃんというのはその通りですが。)まあ普通はシェルスクリプトだけでやるのが難しくなったら、一部を C 言語や別の言語で作ったコマンドに置き換えるか、全部を別の言語に置き換えますよね?そもそもシェルスクリプトから呼び出してるコマンドは C 言語で書かれていますし。つまり C 言語(または他の言語)で作ったコマンドとシェルスクリプトを組み合わせるのが基本パターンです。

ちなみに POSIX の規格の PDF ファイルのページ数をざっくり数えてみたのですが C 言語用のシステムサービス がおよそ 1800 ページ、シェルとコマンドが 1100 ページ(うちシェルの言語仕様で 100ページ)でした。シェルとコマンドも意外とページ数がありますね。

POSIX は移植性が高いソフトウェアの開発を容易にするための標準規格

POSIX は移植性が高いソフトウェアの開発を容易にするための標準規格です。POSIX の目的はこれだけです。

当たり前の話なのですが、なぜわざわざこれを書いたかと言うと **「移植性と後方互換性は異なる概念である」**と言いたいからです。

移植性というのは他の環境(OS 等)への移行のしやすさのことです。一方、後方互換性は同じ環境の新しい製品との互換性(後方互換性が高ければ環境をアップデートしてもシステムは問題なく動く)のことです。移植性と後方互換性はお互いに影響を与えていますが後方互換性は POSIX の直接の目的ではありません。この 2 つを区別してないと POSIX への理解がおかしなものとなります。これを書いた理由はこの記事を最後まで読み進めていくとわかると思います。

POSIX の規格は無料で公開されている

有料で結構高いんじゃないの?って思っている人が多いような気がするのでついでに紹介しておきます。ちなみに昔は有料だったらしいです。

POSIX は大きく 4 つのコンポーネントから構成されています。(フレームの左上参照)

  1. XBD (Base Definitions): 一般的な用語・コンセプトなど
  2. XSH (System Interfaces): 主にC 言語用のシステムサービス、移植性、エラー処理について
  3. XCU (Shell & Utilities): シェルやコマンドについて
  4. XRAT (Rationale): 歴史的情報や規格修正の理由、その他の情報

シェルスクリプトに関する事項は主に XCU 以下に書かれていますが、補足的な情報はその他の所に書いてあることもあります。例えばコマンドラインオプションの仕様は XBD 以下に書いてあったり、XRAT 以下にはシェルとコマンドの策定に関する裏話的なことが書いてあったりして読み物として面白いです(私だけ?)。

POSIX の規格策定(改定)には誰でも無料で参加できる

Another common misconception is that you need to be an IEEE member to participate. Since 1998 the standard has been developed by the Austin Group, an open working group found at http://www.opengroup.org/austin/. Participation is free and open to all interested parties (you just need to join the mailing list). Decisions within the Austin Group are made by consensus; sometimes consensus is easily reached, and sometimes only after heated discussion! The more people involved in such discussions, the more likely that when consensus is reached, it is the right decision that is made. That's why your participation and involvement is so important. Readers should note however that the mailing lists are not a technical support forum. All the major UNIX system players and open source distributions are represented on the Austin Group.

メーリングリストに登録(ここの手順を参考にしました)すれば、次の POSIX 標準規格書のドラフトも確認できるし電話会議に参加することもできます(英語力と勇気があればw)。POSIX に興味がある人は登録してみてはいかがでしょうか?

登録せずともある程度、最新の情報は把握することが出来ます

POSIX のスコープは意外と狭い

POSIX とは Portable Operating System Interface の略でソースコードレベルのアプリケーションの移植性をサポートするために、標準的な OS のインターフェースと環境 (シェルと一般的なコマンドを含む)を定義したものです。

POSIX のスコープ

1.1 Scope

POSIX.1-2017 defines a standard operating system interface and environment, including a command interpreter (or "shell"), and common utility programs to support applications portability at the source code level. It is intended to be used by both application developers and system implementors.

以下の内容は POSIX の対象外です。

  • Graphics interfaces (GUI、グラフィックスインターフェス)
  • Database management system interfaces (データベース管理システム)
  • Record I/O considerations (Record I/O の考慮事項)
  • Object or binary code portability (オブジェクトまたはバイナリの移植性)
  • System configuration and resource availability (システム設定とリソースの可用性)

※ Record I/O が何を意味しているのかよくわかっていません。IBM 用語?

POSIX はアプリケーションの移植をサポートするための OS のインターフェースと環境の定義であってそれ以上でもそれ以下でもありません。GUI、データベース、JSON や HTML 等のパース処理、画像処理、音声処理、機械学習のライブラリやウェブアプリ開発のフレームワークなど、ソフトウェア開発に必須のライブラリは POSIX の対象外です。つまりアプリケーション開発のすべてをサポートするための規格ではありません。これらが POSIX で規定されてないのはソフトウェアに必要ないと思っているわけではなく単に対象外だからです。つまり実際の開発では POSIX 以外の他の規格なども参照しましょうという話です。

ちなみに、これは Unix にとって重要な概念だから POSIX で標準化されているだろうと思うディレクトリ構成も(ほとんど)規定されていません。実は POSIX で規定されているファイル・ディレクトリはたったこれだけです。(注意 このディレクトリ以下は規定されていません)

/, /dev, /dev/console, /dev/null, /dev/tty, /tmp

おなじみの /bin, /etc といったディレクトリや /dev/stdout, /dev/stderr, /dev/random というファイルも POSIX では規定されていないのです。ディレクトリ構成を決める仕様には FHS (Filesystem Hierarchy Standard) がありますが、これは POSIX ではなく Linux Foundation によって管理されています。

POSIX が考えた仕様を実装しているのではなく、すでにある実装を元に POSIX が作られている

たまに POSIX が Unix の共通仕様を作ってそれをみんなが実装していると勘違いしている人がいますが実際には逆です。それは Unix 開発の歴史を学べはすぐに分かります。まず Unix の誕生は 1969 年です。1970 年代 から 1980 年代にかけて AT&T によって開発され大学や研究機関に広まりました。AT&T が開発した System V Release 4 (SVR4)は商業的に最も成功したバージョンと言われていますが、SVR4 の登場は 1988年 です。その後 1993 年に AT&T は Unix の権利を Novellに売却 しました(参考)。BSD は 初期は Unix のソースコードを利用してはいるもの、多くの修正を加えて独自に発展してきました。また Linux では GNU が開発したツールチェーンを採用することが多いですが GNU プロジェクトの開始は 1983 年です。(1992年に GNU Hurd カーネルを除き完成)

そして POSIX が登場したのは 1988 年ですが、シェルに関連する POSIX.2 Shell and Utilities 少し遅れて 1992 年です。つまり POSIX が登場するよりも前にほとんどの実装が作られていたわけです。元の Unix のソースコードは同じであっても、それぞれが好き勝手修正してきているので動作の異なるコマンドがいくつも生まれました。シェルも同様で POSIX が登場するよりも前にシェルが作られました。つまり POSIX で規定されているシェルやコマンドであっても実装によって動作に細かい差異があるのは、POSIX が仕様を完全に決めなかったから各自ばらばらに実装してしまったのではなく、最初からばらばらに実装されていたということです。

POSIX シェルの真実

POSIX で規定されているシェルは Bourne シェルではない

POSIX で規定されているシェルは POSIX シェルと呼ばれており Bourne シェルではありません。Bourne シェルは主に正統な系統の Unix で過去に使われていたシェルです。POSIX シェルは ksh88 をベースとしたサブセットとして定義されており Bourne シェルをベースにはしていません。ksh は Bourne と同じく AT&T で開発されたシェルです。Bourne シェルをベースに開発されたためある程度の互換性がありますが、上位互換になっているわけではなくお互いに完全な互換性がありません。(違いが気になる方はこちら

Bourne シェルは POSIX シェルの標準規格に準拠しておらず、もう殆ど使われてないため切り捨てて良いと思います。POSIX に準拠しているシステムであれば POSIX シェルもインストールされているはずです。使用しているシェルが Bourne シェルであるかどうかわからない場合、パイプ (|)の代わりにキャレット (^) 記号が使えるかで判断することができます。使えてしまったら Bourne シェルです。

ちなみに POSIX の文書(issue 8 draft)で Bourne という用語を検索した所ヒットしたのは 5 件でいずれも歴史的な経緯を説明する文章の中で使われていました。一方 POSIX shell は 21 箇所でした。

殆どのコマンドは POSIX シェルの言語仕様の一部ではない

echo コマンドや test ([) コマンドのようなコマンドは POSIX シェルの言語仕様には含まれていません。これらはシェルスクリプトでよく使われ、シェルにビルトインコマンドとして実装されていますが、それでも POSIX シェルの範囲ではありません。POSIX シェルの言語仕様としてのコマンドは「Special Built-In Utilities」で定義されている 15 個のコマンド (break, :, continue, ., eval, exec, exit, export, readonly, return, set, shift, times, trap, unset) のみです。

POSIX シェルにはそれほど問題がない

実はコマンドと違って POSIX シェルの方はそれほど大きな問題はありません。デフォルトで POSIX 標準に準拠してない動きをするシェルがあるのですが POSIX 準拠モードにすればだいたい OK です。古いシェルや殆ど使われてないシェルまで入れるとバグがあったりしますが通常は切り捨ててよい範囲です。POSIX 定義範囲外のシェル固有の拡張機能に関してはシェル毎に大きな違いがありますが POSIX に準拠したシェルスクリプトを書くのであれば使いません。コマンドと違いシェルは POSIX に準拠していればほとんど問題なくどのシェルでも同じように動かすことが出来ます。

POSIX シェルの仕様も追加されることがある

POSIX シェルの仕様は基本的に安定していますが、それでも機能が追加されることもあります。例えば 2022 年予定の改定では set -o pipefail が追加されます。これは現在の dash や FreeBSD sh では実装されていませんが、それでも必要であると合意が取れれば追加されるようです。実装も間に合う間もしれませんが OS のサポート期間のことを考えると、しばらくの間は「最新の POSIX では定義されてるけど使えないシェルがあるので移植性を重視する場合は使ってはいけない機能」という分類になるでしょう。さすがに互換性を無くすような変更は合意が得られるはずがないのでそのような変更が入ることはありません。

POSIX コマンドの真実

POSIX コマンドは追加されたり削除されることがある

次の大幅な改定は 2022 年後半に予定されています。とは言っても POSIX が改定されたからといって影響が出ることはまずありません。なぜなら仮に POSIX の規格からコマンドが消えたとしても、実装者(OS や GNU 等)はコマンドを消したりしないからです。互換性は保たれており POSIX で規定されなくなったコマンドを使っていても今までのソフトウェアはそのまま動きます。単に今まで移植性があると信じて書いたコードが、実は移植性がなかったと知らされるだけです。

さて興味本位で今まででどのようなコマンドが追加・削除されたのかをちょっと調べてみました。古い POSIX の仕様は無料で公開されてないようなので 1995 年の Single UNIX Specification Issue 4 からです(ほんと POSIX との対応が分かりづらい・・・)

  1. 1988 POSIX.1 (POSIX C 言語インターフェース 標準化)
  2. 1992 Shell and Utilities POSIX.2-1992 (シェルとコマンドがはじめて POSIX で標準化された時)
  3. 1995 Single UNIX Specification: Commands and Utilities Issue 4, Version 2
  4. 1997 Single UNIX Specification Version 2: Commands and Utilities Issue 5
    1. 削除 banner, chroot, red, sdb, wall
    2. 追加 fuser, ipcrm, ipcs, link, unlink
  5. 2001 Single UNIX Specification Version 3: Commands and Utilities Issue 6 POSIX.1-2001
    1. 削除 c89, calendar, cancel, cc, col, cpio, cu, dircmp, dirs, egrep, line, lint, lpstat, mail, pack, pcat, pg, spell, sum, tar, unpack, uulog, uuname, uupick, uuto
    2. 追加 c99, qalter, qdel, qhold, qmove, qmsg, qrerun, qrls, qselect, qsig, qstat, qsub,
  6. 2008 Single UNIX Specification version 4: Commands and Utilities Issue 7 POSIX.1-2008 (正誤表対応版 POSIX.1-2017)
    1. 削除 なし
    2. 追加 なし
  7. 2022 Issue 8 POSIX.1-202x 現在ドラフト
    1. 削除 誰も使わないコマンド 12 個(書いても問題ないと思うんですがちょっと怖いので誰かが書いてるの見つけるまで延期w)
    2. 追加 なし

削除した理由(正確には評価した結果)は Exclusion of Utilities に書いてあるようです。理由が書いてあるけど上のリストにないコマンド(adb, as, cpp, dc, emacs, ld, login, lorder, mknod, news, passwd, prof, RCS, rsh, sdiff, shar, shl, size, su)は最初から POSIX で規定されてないのか削除されたのかどちらかわかりませんがついでに書いておきます。(vi が POSIX で規定されてるのに emacs がされてない理由も書いてありますよw)2022 年の更新は大して変わりませんが過去にはいくつか気になるコマンドがありますね。上でも書きましたが、POSIX からコマンドが削除されても別に心配する必要はありません。実際の環境には残っています。

ところで新しい POSIX コマンドはどうやって追加されるのでしょうか? 例えば seq コマンドは POSIX では標準化されていません。seq コマンドは 1985 年に Research Unix 8th Edition ではじめて登場したコマンド(参考)ですが、商用 Unix や BSD などに採用されなかったとあるため、おそらくこれが原因で標準化されなかったのでしょう。しかし GNU では 1994 年に実装されており BSD 系でも実装されているため今では FreeBSD や macOS でも使うことが出来ます。余談ですが、たまたま起動していた FreeBSD 10.4 の seq の man ページを見ていたのですが Research Unix 8th Edition ではなく Plan 9 ではじめて登場と書いてあったのであれ?と思いましたが FreeBSD 14.0 の seq の man ページだと訂正されてるので間違いだったようです。どうやら NetBSD では 3.0 (2005 年)、FreeBSD では (2012 年) から使えるようになったようです。

seq コマンドがない環境が今もあるのかわかりませんが、いつまでも古い環境を考慮する意味はないですし POSIX に追加されたりしないんでしょうかね?まあ seq コマンド程度であれば簡単に自作できるのでいらないと言えばいらないですが。それよりも個人的には最近提案された readlink コマンドの追加の方が気になっています。readlink は実は複数ファイルで構成されるシェルスクリプト製のソフトウェアにとっては必須のコマンドです。POSIX コマンドだけで作れないことはないので作りましたが、これこそ標準で入っていて欲しいコマンドですね。

POSIX コマンドは変更されることがある

さすがに互換性をなくすような変更はありえないですが、標準化されていた内容が変わることがあります。まだ詳しく調べてないないためどれだけあるのかは不明ですが。気づいて「へー」と思ったのは [ "$FLAG" = "on" -a "$value" -gt 10 ]-a (and) です。時々シェルスクリプトで使ってるのを見かけますが実は最新の POSIX.1-2017 の test では POSIX に厳格に準拠するアプリケーションは使ってはならない (shall not) とされています。

もちろん今すぐ修正する必要はありません。なぜならシェルの実装者は後方互換性維持のために、これらの機能を削除することはほとんどありえないからです。POSIX で使用を禁止されたからと言ってその機能が使えなくなるなるわけではありません。ただ移植する時に問題はなかったと信じていたことが、実は問題があるかもしれなかったという事実が明らかになっただけです。

POSIX でコマンドの挙動は統一されていない

POSIX コマンドの仕様には実装依存 (implementation-defined) や 未定義 (unspecified) という用語がかなり登場します。必ずしも動作が必ず一つに定義されているわけではありません。なぜこのようなことになっているかと言うと、ほとんどのコマンドは POSIX が登場するよりも前に作られているからです。先にいろいろな実装があって、それらを統一すること無く矛盾しないような内容の仕様を作ってるので、異なる実装があればそれを許容するような内容になっています。

一例を言うと一番基本的なコマンドと言える echo コマンドでさえ -n や それ以外のオプションが指定された場合やエスケープシーケンスが含まれている場合の動作は実装依存と定義されています。

If the first operand is -n, or if any of the operands contain a <backslash> character, the results are implementation-defined.

組み込み環境における問題

組み込み環境では POSIX とは別の問題があります。それは環境自体が POSIX に完全に準拠しているとは限らないということです。組み込み環境では使用できるディスク容量やシステムメモリ量に制限がある場合があります。そういった環境では標準的なコマンドを単一の実行ファイルに詰め込んだ BusyBox や ToyBox がよく使われます。これらのソフトウェアは容量を減らすために POSIX で標準化されているコマンドであってもすべてのオプションを実装しているとは限りません。またあまり使わないコマンドは実装されておらずビルドオプションによって組み込むコマンドを減らしてサイズをさらに削減することも出来ます。逆によく使うようなコマンドは POSIX で定義されていなくても実装されてたりします。ちなみに BusyBox は ash ベースの POSIX シェルが搭載されていますがシェル自体はちゃんと POSIX に準拠しています。(ちなみに ToyBox ではシェルはまだ未完成)

こういった環境では POSIX に準拠したコマンドとオプションだけを使ったシェルスクリプトであっても動かない可能性あります。そのため POSIX コマンドのサブセットを使用しなければいけませんが、実際になにが使えるかは具体的な環境によって異なります。こういう環境まで考慮すると POSIX に準拠しただけでは移植性がないという結論になってしまいます。もちろんこういう環境は特殊だから考慮しないというのは妥当な判断だと思います。(ShellSpec はこういう環境も考慮しましたけど)

後方互換性を実現しているのは本当は誰なのか?

POSIX は「移植性」を高くするためインターフェースであって実際にソフトウェアを実装していません。実装していない以上、実装の利用者もいないので「後方互換性」の話とは無縁です。「後方互換性」を実現しているのは誰か?といえばもちろん POSIX に準拠したソフトウェアを実装している OS: Debian/macOS/FreeBSD 等、シェル: dash/bash 等、コマンド: GNU 等 です。後方互換性、言い換えるとシステムをアップデートしてもソフトウェアがそのまま動くのは POSIX とは直接関係なく、実装者の努力の賜物であり彼らの成果であるということです。互換性を維持してくれてありがとう!

POSIX があるから後方互換性が維持されているのでは?と思うかもしれませんがそうではありません。その一例として有名なのが tr コマンドです。このコマンドは System V 系(歴史的な動作)と BSD で書式が異なります。例えばtr コマンドで小文字から大文字に変換する場合、POSIX に準拠した書き方は tr a-z A-Z ですが、Solaris 10/11 では tr '[a-z]' '[A-Z]' とブラケットが必要となります。他にも細かい挙動が異なりPOSIX tr のRATIONALEを見ると tr コマンドを POSIX で標準化したときの苦労が垣間見えます。tr コマンドは sed で置き換えることも出来るわけで POSIX から外しても良かったのではないかと思わなくもないですが POSIX では 範囲指定に BSD 方式(ブラケットなし)を採用しました。大量の BSD スクリプトを壊さないためとその根拠が述べられています。(注意 BSD でも tr '[a-z]' '[A-Z]' で正しく変換できることに注意して下さい。完全ではありませんがマシな方法で標準化したということでしょう。)

さて話はここからです。歴史的な動作を採用していた Solaris 10/11 はどのような対応をしたでしょうか? POSIX で標準化されたから標準に合わせて変更したでしょうか?もしそんなことをしてしますと後方互換性が失われ OS をアップデートした時にシェルスクリプトが壊れますよね?だから Solaris (OS の実装者)はそのような変更しませんでした。つまり真に「後方互換性」を重視しているのは実装者であるということです。

では Solaris は POSIX に準拠してないのか?というとそうではありません。POSIX 準拠モード(実際にそう呼ばれているわけではありません)に相当する仕組みを作ることで「後方互換性」を維持しながら「POSIX 準拠」も実現しました。具体的には /usr/bin/tr は歴史的な動作を尊重して System V 系の動作を維持し /usr/xpg4/bin/tr に POSIX 準拠の tr コマンドを別に配置しました(参考)。この 2 つのコマンドを切り替えるには環境変数 PATH を利用して優先するコマンドを切り替えます。そのためには getconf コマンドを使用します。デフォルトの PATH/usr/sbin:/usr/bin で「後方互換性」を維持し getconf PATH を使用して POSIX 準拠のパスに設定することで「POSIX 準拠」も実現しているわけです。これが Solaris における POSIX 準拠モードに相当する仕組みです。

残念なのはこの POSIX 準拠モードを実現する仕組み自体が標準化されておらず、システムによってまちまちであるということです。例えば HP-UX では環境変数 UNIX_STD で AIX では環境変数 XPG_SUS_ENV で設定するようです(HP-UX や AIX を使ったことがないので検証はしていません)。Linux(GNU)では環境変数 POSIXLY_CORRECT を使って設定します。(つまり Linux はデフォルトでは POSIX に完全に準拠していないと言うことを意味します。詳細)macOS は 環境変数 COMMAND_MODE を使います。FreeBSD は全体の設定を変える環境変数があるのかわかりませんでしたが、例えば sort コマンドは GNUSORT_NUMERIC_COMPATIBILITY という環境変数で GNU 相当の挙動に変わるようです。もし POSIX が現実の後方互換性を意識しているのであれば、POSIX に準拠してないシステムへの対応もしっかり標準化したでしょう。詳細は省きますが HTML5 では過去のブラウザとの互換性も考慮して仕様を策定しています。同じ標準化団体でもアプローチは大きく異なります。

このように 実装者は POSIX よりも「後方互換性」を重視しています。そもそも POSIX がやってるのは移植性を高くすることで目的が異なるので仕方ない面もありますが、システムを更新してもソフトウェアが問題なく動き続けるのは POSIX ではなくいろんな実装者のおかげであるとういことを忘れてはいけません。

シェルスクリプトの移植性問題を解決するための案

さて POSIX コマンドの問題点が明らかになったところで、シェルスクリプトの移植性問題を解決するための案の紹介です。案ですのでまだしっかりとした検証は行っていません。

案 (可能な場合は)GNU Coreutils をインストールし GNU Coreutils に依存する

このアプローチが有効である理由

  • GNU Coreutils は C 言語で書かれている(C 言語は POSIX で規定されている言語)
  • GNU Coreutils はほとんどの環境に移植されている(移植性が高い)
  • GNU Coreutils は環境の違いを吸収している(シェルスクリプト界の jQuery)
  • GNU Coreutils は POSIX コマンドよりも多くて高機能(生産性が高い)
  • GNU Coreutils は 十分テストされている(高い信頼性)
  • GNU は後方互換性を重視している(そもそも GNU が POSIX コマンドを作っている)
  • GNU はオープンソースで多数のユーザーが居る(独占されておらず誰でも後を引き継げる)
  • GNU は標準化機関による標準規格ではないがデファクトスタンダード(事実上の標準)である
  • その他にも移植性や後方互換性が高いと認められるコマンドも使って良い(例 curl

注意

  • 標準のコマンドを置き換えるという意味ではない(別ディレクトリや別名での追加)
  • GNU ps コマンドは移植されていない(情報を取得する仕組みが OS によって異なるため移植できない)
  • GNU cp コマンドは一部のシステム固有のファイルのメタデータを扱えない可能性がある(要検証+対策が必要)
  • シェルビルトインコマンドの違いは吸収できない

このアプローチのメリットを説明する前に、このアプローチが使えない場合の例を提示します。

  • 組み込み環境など追加のパッケージがインストール出来ない場合
  • 実行要件に追加コマンドのインストールを加えたくない場合(オープンソースのソフトウェア等)
  • Alpine ベースの小さな Docker イメージを作りたい場合
  • POSIX コマンドだけを使うという自分の主義に反する(GNU コマンドを使うと負けた気がする)

上記に当てはまる場合には GNU Coreutils をインストールするというアプローチは使えませんので別の方法を取る必要があります。「なんだ、使えない場合があるじゃないか。それじゃだめだよ」と言いたい方にオススメするのが Unix 哲学の「90 パーセントの解を目指す」という考え方です。

90 パーセントの解を目指す

**「ガンカーズの UNIX 哲学 (Mike Gancarz: The UNIX Philosophy)」**の一つに「90 パーセントの解を目指す」というものがあります。(ただし「重要度の低い 10 の小定理」であり「必ずしもすべての UNIX 関係者が教義として受け入れているわけではない」と書かれているので注意)

この考え方は「UNIX という考え方」の中で詳しく述べられています。該当部分 (P11) を引用しますと、

どんなことであれ、100パーセントうまくやることは大変だ。90パーセントのことだけをうまくやれるようにするほうが、はるかに能率的であり費用対効果も最も高い。UNIX開発者は、対象ユーザーの90パーセントが満足する解を目指す。残りの10パーセントには自分で何とかしてもらうしかない。

この言葉に当てはめると 100 パーセントの移植性を実現しようするのは難しく、90 パーセントの移植性でよければ、はるかに能率的で費用対効果も高いと言うことです。POSIX 準拠のコマンドだけを使っても生産性が落ちないような場合にわざわざ GNU Coreutils を使う必要はありませんが POSIX 準拠に固執するあまり生産性が落ちてしまったら本末転倒ですよね?ましてや何かの機能が作れないなら意味がありません。移植性が高いどこでも動くソフトウェアを作るのが目的なのに(作れなかった機能は)どこでも動かないんですから。生産性が落ちるのが良くないんだなということで POSIX 準拠のコマンドだけを使って動くツールを作るという手段を思いつきますが「これをインストールしてください」と言うならば「GNU Coreutils をインストールしてください」と言うのと何も変わりません。

確かに GNU Coreutils のインストールが不可能な環境もありますし、オープンソースのソフトウェアなどで利便性のために追加のパッケージのインストールを必要としたくない場合もあります。その場合には使えませんが、業務システムなど専用の環境を自分で用意するのであればディスクにも余裕がありますし特に制限もないはずです。GNU Coreutils をインストールすれば解決する場合にそうしないメリットはありません。さて GNU Coreutils のインストールができない環境なんてどれくらいあるのでしょうか? Linux の大部分には最初から入っていますし、macOS、FreeBSD、Solaris、調べた限り HP-UX や AIX にもパッケージがあるようです。100 パーセントではなくとも 90 パーセントの環境は GNU Coreutils をインストールすることで解決します。

GNU Coreutils をインストールすることによるメリット

GNU Coreutils をインストールというのは至って平凡な解決策であるため、その程度ならすでにやってるよという人も多いとは思いますが POSIX 準拠にこだわっている人にとっては受け入れがたい解決策かもしれません。でも待って下さい、GNU コマンドを使ったとしても GNU の拡張機能を使わなければ POSIX 準拠のシェルスクリプトになりますよね?GNU Coreutils をインストールする目的は POSIX に準拠しているコマンドの実装による挙動の違いを無くすことです。違いをなくすというだけなので POSIX 準拠であることには変わりませんし、シェルスクリプトはコマンドの違いを気にする必要がなくなるのでコードはシンプルになります。実装が多いと大変です。**POSIX コマンドは挙動が一つに統一されていないため実装の数だけテストを行わなければ動くという保証はできません。**GNU Coreutils をインストールすることは、そのテスト環境の数を減らすことに繋がります。(もちろんテスト環境が一つだからといって一回だけテストすれば終わりにはなるわけではありません。開発を継続し続けるならテストの自動化は必須です。)

次に GNU Coreutils を使った場合 GNU コマンドの拡張機能が使え生産性が上がるというメリットがあります。と言ったら POSIX 準拠にならないじゃないかと反論したくなる思います。でもちょっと待って下さい、本当の目的は高い移植性と高い後方互換性を実現することですよね?POSIX 準拠はその手段であって目的ではないはずです。「POSIX 準拠のものだけを使う」のは目的ではなく「いろんな環境で動く」のが目的であるはずです。GNU Coreutils がいろんな環境で動いている限り目的は達成できます。

GNU Coreutils がコマンドの違いを吸収し、さらに便利な機能を提供しているものである考えると、シェルスクリプト界における jQuery と同じ立場であるということに気づきます。jQuery が普及したのは当時のブラウザ間の互換性のなさを吸収し便利な機能を提供し、高い移植性と互換性を実現していたからです。jQuery(prototype.js も忘れてませんよ)が登場する前はプログラマが自分で各ブラウザの挙動を調べ、なるべく共通に使えるものだけを使って、それぞれのブラウザでやり方が異なるものは、IE 用コード、ネスケ用コードと複数のコードを書いて対応していました。大変な時代でしたが jQuery の登場によってそれは解決しました。シェルスクリプトでも jQuery 相当の GNU Coreutils をインストールすれば問題(のほとんど)は解決するでしょう。

将来 GNU Coreutils がなくなったりして使えなくなるとか後方互換性がない変更をいれる心配もまずありません。膨大な数のユーザーがいて実績があるからです。GNU プロジェクトの(開発)開始は 1984 年 なので 1988 年に公開の POSIX (作業開始は 1985 年頃)よりも歴史が長いプロジェクトです。そもそも POSIX に準拠したコマンドを作っている所の一つが GNU です。そんな所が互換性を壊すような変更を入れるでしょうか?「お前が信じる POSIX に準拠したコマンドを作った GNU を信じろ」というやつです。GNU Coreutils を壊す行為はもはやリチャード・ストールマン(GNU を作った中心的な人物)でも不可能でしょう。何かがあったとしてもプロジェクトはフォークされてあるべき流れに戻されて継続してしまうからです。GNU Coreutils はデファクトスタンダードとなっておりもはや誰かに独占されたものでもありません。どうしても心配なら今の GNU Coreutils の実行バイナリとソースコードをダウンロードしておけばよいでしょう。なにかがあっても自分で対応できる。オープンソースであることのメリットの一つです。(おまけ。正直 GNU がなくなるよりも GNU 以外がなくなるほうが可能性が高いかと・・・)

結論としては POSIX コマンドだけを使っていれば確かにどこでも何年後でも動くでしょうが GNU Coreutils を使ったとしてもどこでも何年後でも動くということです。GNU Coreutils がどこでも動き、後方互換性が維持されているのだから当然の帰結です。もちろん GNU Coreutils をインストールするだけでは解決できない問題も存在します。それらを解決する方法については「第四弾」で解説します。

オープンソースソフトウェアを使うことのメリット

この考えを推し進めるとオープンソースで多数のユーザーがいて移植性や後方互換性が高いソフトウェア(例 curl)等も使ってよいという結論に達します。「POSIX で規定されてないコマンドじゃん」と言うかもしれませんが、もうそろそろ Unix 文化には POSIX コマンドだけを使うという文化はないことに気づきましょう。Unix 哲学には「ソフトウェアを梃子(てこ)として使う」という言葉があります。ここでいうソフトウェアは別に POSIX コマンドだけに制限されていません。当時 POSIX がなかったのですから当然です。POSIX で定義されているコマンドだけを使うという方針は移植性の点からは意味がありますが、Unix 哲学とは正反対の考え方です。「UNIX という考え方」の P69 では 「他人が作ったコードを利用する」 または 「コードを他の人が使うことを認める」 ことの重要さが述べられています。(オープンソース)ソフトウェアを梃子として使いましょう。

一応言っておきますがオープンソースだからといってすべてが将来も安心して使えるということにはなりません。互換性がない変更を予告なしに入れたり、作ったっきりで放置で誰も使ってないようなプロジェクトは多数あります(そういうプロジェクトであってもフォークして自分でメンテナンスするというのであれば使って構いませんが他の代替プロジェクトを探したほうが早いでしょう。オープンソースでない場合はそれすらできないので更に危険です)。そういう信頼性の低いプロジェクトと curl のようなプロジェクトとを一緒にしてはいけません。移植性や後方互換性が高いかはプロジェクトごとに判断するべきことです。プロジェクトの方針から移植性や互換性を重視したものとわかるか?実際に複数の環境での動作実績があるか?他の環境でパッケージが提供されているか?テストコードはちゃんとあるか?それらは自動化されているか?メンテナンスはされているか?そういった所からプロジェクトの信頼性を判断することが出来ます。そういう判断ができるようになるのもプログラマとしての技術の一つです。

もちろん将来がどうなるかは誰にもわかりませんから、そうやって信じて使ったソフトウェアが将来使えなくなる可能性はあります。ただそれも「90 パーセントの解を目指す」です。100 パーセントを目指したために他の人が作ったソフトウェアが信じられなくなり自分で作ってばかり(いわゆる独自技術症候群)いたら費用対効果は悪くなります。将来必要になるとわかってないことへの対応(動かす予定がない環境への対応や将来コマンドが使えなくなったらどうしようとかいう不安を解消するためだけの対応)を必要になってない段階でするのは無駄な作業です。そういうのは YAGNI(You ain't gonna need it - それは必要にならない) と言われる悪い原則にあてはまります。もし作ったソフトウェアが数年後に他の環境に移動してなかったり 以前使えていた GNU コマンドが今も使えていたりしたら、それは YAGNI だったということの証明です。正直な所、作ったシステムを全く別の環境に移動することなんてまずないでしょう?環境のバージョンアップはありますが GNU Coreutils 等の信頼性が高いものを選べば後方互換性は十分維持されます。めったにやらない環境の更新なんかよりも通常の開発の生産性の方が圧倒的に重要です。

結局の所、適切なオープンソースのソフトウェア選べば 90 パーセントの環境で動いて将来のバージョンアップでも問題なく動くということです。もしどうしても不安であれば、後からすぐに対応が可能になるような設計にしておけば十分です。わからない将来のために(無駄になる可能性が高い)準備を今からしておくよりも、メンテナンスしやすいような作りにしておく方が費用対効果は高いです。それらの方法については「第四弾」で解説します。

注意 念の為ですが私はクローズドソースや独自技術を完全に否定しているわけではありません。それが十分に価値があるものであればクローズドの独自技術にしても構いませんし、それを使っても構いません(例えば私は Windows を使っています)。ただそういうものに依存してしまうと将来性にリスクがあるので、使う場合はリスクに対して価値があるか十分検討しましょうということです。

足りないパーツ

GNU Coreutils をインストールするという案ですが、実は足りないパーツがあります。インストールしたコマンドをどうやって使うのか?ということです。インストールしたコマンドは別のディレクトリか別の名前(例 sed なら gsed)でインストールされているわけですが、シェルスクリプトからそれを使うようにしなければいけません。

基本的にシェルスクリプトの冒頭で sed() { gsed "$@"; } のようなコードを羅列していけばいいだけと思うんですが、環境によってコマンド名が違ったりする可能性があるのでなんとも。Homebrew ですべてのコマンドを GNU コマンド優先にするのであれば以下のコードをシェルスクリプトに書けば楽ですが、これだと全部置き換わってしまいますしもう少し柔軟な解決方法が欲しい所でが、まああらゆる場所で動かそうと思わなければ対応はそんなに大変ではないと思います。

PATH="$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH"

もう一つの問題はシェルビルトインコマンド(echo 等)の対応です。GNU Coreutils をインストールしてもシェルビルトインコマンドの方が優先されてしまうからです。まあ echo の問題は printf '%s\n' を使うことでほぼ解決しますし、ざっと見る限りそれ以外はさほど大きな問題はなかったと思います。

その他のアプローチ

大きな問題については GNU Coreutils をインストールすることで解決するわけですが他にもいくつか案があります。

  1. シェル関数ライブラリを作る
  2. POSIX コマンドの挙動の違いを吸収するシェル関数ライブラリを作る
  3. シェルスクリプトで GNU 互換コマンドを作る

まず 1. ですが実はこれを私は作ろうと思っています。シェルスクリプトで非互換性に対抗するためのシェル関数ライブラリです。外部コマンドの互換性問題を吸収する GNU Coreutils が シェルスクリプト版 jQuery だとすると、シェル関数ライブラリは言語自体の互換性問題を吸収し更に便利な機能も提供するということで、シェルスクリプト版 underscore.js (lodash) です。なので Unicode の _ (U+005F) の名前からとってプロジェクト名は lowline です。外部コマンドがシェルスクリプト用のグローバル関数だと考えると、シェル関数はプライベート関数に相当するわけで lowline の全ての関数は _ で始まります。underscore.js_. で始まるのと整合性が取れているでしょう?ここまでは決まってるんですけどプロジェクトを開始するまでもう少し時間がかかると思います。この名前結構気に入ってるので取らないでくださいねw

次に 2. ですが POSIX コマンドの挙動、つまりオプションや入出力をすべて統一します。GNU Coreutils へのアップグレードを考えると GNU コマンドの挙動に正規化するのが現実的でしょう。本来これを POSIX がやってくれればよかったのですけどね。これができれば単一の POSIX 準拠のシェルスクリプトでどこでも動くようになります。ただこれはどこまでやるべきかという問題が出てきて POSIX コマンドだけ対応するという方針であればまだわかりやすいんですが、もう少し追加のコマンドやオプションにも対応してもいいのでは?などと考えてしまうと際限がなくなってしまいます。実際の環境のコマンドの挙動も調べないといけないので作業も大変になりそうです。POSIX には定義されてないけどどこでも使えるコマンド(which とか seq とか)の一覧でもあればいいんですけどね。なんつーかやっぱり コマンドを自由に使えた Unix 時代より不便になってる気がします。

最後に 3. のシェルスクリプトで GNU 互換コマンドを作るというアイデアも思いついたのですが、これはあまり意味がないと思ってるのでやらないと思います。まずこれは車輪の再発明です。車輪の再発明をするのであれば、それ相応の合理的な理由がなければいけません。シェルスクリプトで GNU 互換コマンドを作らずとも GNU Coreutils をインストールすれば完全で十分テストされた品質の高いものが手に入ります。インストール権限が問題になるのであればスタティックバイナリを作って配布すればシェルスクリプトファイルを配布するのと違いがありません。GNU 互換コマンドにするには多くのオプションを実装しなければいけないので大変です。オプションのパースだけで何十行にもなって処理も遅くなるでしょう。しかも実装した所で実際に使う機能はほんの僅かだったりします。であれば多数のオプションを実装した GNU 互換コマンドを作るよりも、本当に必要な処理だけを実装したシェル関数を作るほうがより現実的です。例えば GNU 版の seq コマンドは --format オプションを実装していますが、実際には連番を返すだけの小さなシェル関数があれば必要十分であると思います。一つのことだけを行う小さな関数です。

補足ですが、私が作ろうとしているのは基本的にシェル関数です。理由はシェル関数の方がパフォーマンスが高いのとシェル関数でないと使えないテクニックもあるからです。それらを無視すれば原理的にはほとんどは外部コマンドでも実装することが可能だと思いますが、別ファイルとして管理する必要があるので配布するシェルスクリプトに組み込むことが出来るシェル関数のほうが使い勝手がよいです。

さいごに

引っ張るに引っ張ってでてきた案が GNU Coreutils のインストールだったのには拍子抜けだったかもしれません。Linux の場合は通常インストールされているから普通に POSIX 準拠を考えずに GNU コマンド使えば良いという結論ですからね。しかしこの案は全てに使える解決策ではありません。例えば業務システムなど GNU Coreutils のインストールが可能な専用の環境を自分で用意することが可能な場合の解決策です。繰り返しますが以下の場合には使えません。

  • 組み込み環境など追加のパッケージがインストール出来ない場合
  • 実行要件に追加コマンドのインストールを加えたくない場合(オープンソースのソフトウェア等)
  • Alpine ベースの小さな Docker イメージを作りたい場合
  • POSIX コマンドだけを使うという自分の主義に反する(GNU コマンドを使うと負けた気がする)

このようなものに当てはまるのは少ないかもしれませんが、当てはまるとしたらオープンソースソフトウェアを配布する場合が一番多いのではないでしょうか? そしてまさに ShellSpec が該当します。さらに ShellSpec は組み込み環境にも対応していますし、Alpine ベースの小さな Docker イメージも作りたいのです。そして GNU コマンドを使うと負けた気がするので全部に当てはまっていますw

ShellSpec は 100 パーセントの解(自分でなんとかするしかない残りの 10 パーセント)を目指しました。実現方法を少し説明すると外部コマンドを(可能な限り)使わないという極端なアプローチを取っています。コマンドが環境依存するなら使わなければいい。単純な理屈ですよね?w もちろん外部コマンドの使用を完全にゼロにできたわけではありませんしビルトインコマンドは使わざるを得ないのでどうしても移植性の問題は避けられません。一部のシェルの非互換性やバグもありますし、それなりの量のワークアラウンドを実装しています。またデフォルト状態で動くようにするために車輪の再発明も行っています。おかげで生産性は悪いわけですが移植性と信頼性重視のシェルスクリプト用テストフレームワークを作るのが目的ですから致し方ありません。生産性の低さはその他の部分を工夫することでカバーしました。今ではほとんどの問題はほぼ解決してしまっているので苦労せず高いメンテナンス性を維持できています。それらの具体的な方法については、第二~第四弾で詳しく説明します。

まとめ

  • POSIX にこだわるあまり生産性が落ちたら本末転倒
  • POSIX コマンドは標準化されていても実装によって挙動が異なる
  • (Unix 哲学「90 パーセントの解を目指す」)
    • 組み込み環境などどちらにしろ POSIX に準拠してない環境がある
    • GNU Coreutils をインストールすれば殆どの環境で互換性が保てる
  • (Unix 哲学「ソフトウェアを梃子(てこ)として使う」)
    • 信頼性が高いオープンソースソフトウェアを使おう
  • 100 パーセントの解を目指すための方法は第二~第四弾で!
145
140
4

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
145
140