はじめに
Linux / Unix をターミナルから使う時に使用するソフトウェアがシェルです。シェルの役目は CLI ベースのユーザーインターフェースとしてユーザーからの操作でプログラム(主に CLI コマンド)の実行を仲介したり、その操作を自動化するためのシェルスクリプトを実行する機能を持っています。現在最も使用されているシェルは GNU プロジェクトが開発している bash ですが、OS によって異なるさまざまなシェルが使われています。
シェルの最低限の仕様は POSIX で標準化されています。この標準規格に準拠しているシェルは「POSIX(準拠)シェル」と一般的に呼ばれています。シェルは大別すると「POSIX シェル」と「POSIX シェル以外」に分けられます。UNIX 全盛期時代、かつてシェルが「UNIX シェル」と呼ばれていた頃は「B シェル」と「C シェル」という分け方もありましたが、これはもはや過去のものと考えて良いでしょう。シェルの世界も少しずつ変わっています。近いうちに POSIX 標準規格も改訂される予定です。
この記事では主要シェルの歴史や種類、POSIX がシェルにどのように関わっているのか、そしてシェルの現在と過去ついて詳しくまとめ、シェルの未来についての私の考えを述べています。また、移植性が高いすべての環境で動くシェルスクリプトを書きたい人、シェルスクリプトや POSIX コマンドの移植性について困っている人、POSIX 標準規格に不満がある人への理解の助けになるでしょう。シェルスクリプトは他の一般的な言語に置き換えることが不可能なものです。それは歴史が証明しています。ならばシェルを理解し良いもの取り入れて未来を改善していかねばなりません。
もしかしたらこの記事の内容が、他の記事など比較して違うことを言っていると感じるかもしれませんが、それは最新の情報を組み込んでアップデートしているからです。大学などの教育機関が公開している情報でさえ(だからこそ?)内容が古いことが多いです。もっともこの記事に間違いがないという保証はありません。
シェルの種類と系統図
上記の図は What does it mean to be "sh compatible"? の unix-shells.svg を参考に私が新たに書き直したものです。
主要な POSIX 準拠シェルの特徴と最新版のリリース日
- ash - Almquist Shell 系 (FreeBSD sh, NetBSD sh)
- BSD 系 Unix で多く採用されたシェルで元は Bourne シェルのクローン。拡張機能が少なく速い
- FreeBSD や NetBSD に組み込まれており他の OS への移植は(おそらく)行われていない
- 最新版はそれぞれの OS のリリース日
- [補足] 初期の ash は POSIX 準拠ではなく BusyBox ash は拡張機能が ash 系としては多い
- dash - Debian Almquist shell
- Debian Linux に移植された ash。ash と同様に拡張機能が少なく速い
- Debian 系 Linux で使われているシェルだが他の OS にも移植されている
- 最新版は 0.5.11.5 で 2021年9月 リリース
- bash - Bourne Again Shell
- GNU プロジェクトで開発されたシェル。拡張機能は多いが ksh や zsh ほど多くはない
- Linux で広く採用されたが、ほとんどの OS に移植されている
- 最新版は 5.2 で 2022年9月 リリース
- ksh - KornShell (ksh88, ksh93)
- 主に AT&T や 商用 UNIX で使われている Bourne シェルの後継シェル。拡張機能が多い
- 元々はプロプライエタリだがオープンソースになってからは多くの OS に移植されている
- 最新版は ksh93u+m 1.0.4 で 2022年10月 リリース
- [補足] OpenBSD ksh は ksh93 ではなく pdksh (機能的には ksh88 相当)
- zsh - Z shell
- bash、ksh、tcsh の機能を取り込んだ高機能なシェル。拡張機能が多い
- 多くの OS に移植されているが
/bin/sh
としては(おそらく)使われていない - 最新版は 5.9 で 2022年5月 リリース
注意 上記のシェルは現在は POSIX 準拠シェルですが、開発が始まった当初は POSIX 標準化前であり POSIX 準拠ではありません。また Bourne シェルも(Schily Bourne Shell を除き) POSIX 準拠ではありません。
「B シェル」「C シェル」 という呼び名は何だったのか?
今でも「シェルは B シェル系 と C シェル系の二種類に分類されます」というような説明を見かけますが、この呼び名はもう捨て去るべきだと思います。この呼び名が生まれたのはおそらく UNIX 全盛期時代の 1980 〜 1990 年代初期だと思います。「K シェル」という呼び名も見かけます。ash に対応する「A シェル」は使っている人もいなくはないと言った感じでしょうか? rcシェルに対応する「R シェル」は多分なさそうですね。
この呼び名が意味することは不明確で「B シェル系」「C シェル系」と言ったカテゴリ名として使われている場合もあります。しかしおそらく最初は、正式名称「Borune シェル」という名前が長い(またはどこかの UNIX で bsh
という別名があった)から B シェルという呼び名が生まれ、そのような呼び名になった理由は csh の正式名称が C シェルだったからでしょう。
生まれた当初は C シェルに対して B シェルという略称があっただけという単純な話だったのだと思います。その流れで新たに誕生した KornShell (ksh) が K シェルと呼ばれ始め、一部(?)の人は BSD Unix で使われた ash を A シェルと呼びました。私はこれらは(C シェルを除いて)和製略語ではないかと思っています。と思っていましたが英語版 wikipedia の Almquist shell (ash) には「also known as A Shell, ash and sh」と書かれていますね。海外でも B Shell で通じるのだろうか?うーん。
シェルの略称 | シェルの正式名称 |
---|---|
B シェル | Bourne シェル (sh) |
C シェル | C シェル (csh) |
K シェル(まれにこう呼ばれることがある) | KornShell (ksh) |
A シェル(まれにこう呼ばれることがある) | Almquist shell (ash) |
ややこしいことに"初期の" ash は Bourne シェルのクローンとして作られたため、Bourne シェルと同じく ash も sh
というコマンド名で使われました。いつしか Bourne シェルの後継シェル(ash、bash、ksh、zsh など)全てが B シェル系というカテゴリになったように思えます。一方で C シェルも改良版の tcsh が登場したため C シェル系というカテゴリになりました。
シェルのカテゴリ | シェル |
---|---|
B シェル系 | Bourne シェル互換シェル (ash, dash, bash, ksh, pdksh, mksh, zsh) |
C シェル系 | C シェル互換シェル (csh, tcsh 二つしかない) |
しかしですね、現在 B シェルの頭文字である Bourne シェルはほとんど使われておらず、Bourne シェル互換の後継シェルは POSIX 準拠の POSIX シェルとなりましたが、Bourne シェルと POSIX シェルには多くの仕様の違いがあるため一つのカテゴリにするのは不適切です。C シェルは tcsh に置き換えられ事実上一つのシェルしかなくカテゴリにするまでもありません。また一つのシェルというのなら他にも fish など独立したシェルはいくつもあります。初期の ash は Bourne シェルのクローンでしたが、初期の ash はもう使われておらず、今ある ash 系はすべて ash の後継シェルであり、POSIX シェルに変わりました。
シェルのカテゴリ | シェル |
---|---|
B シェル系 1 | Bourne シェル互換シェル (Bourne シェル と初期の ash) |
B シェル系 2 | Bourne シェル後継の POSIX シェル(多数) |
C シェル系 | C シェル互換シェル (csh, tcsh 二つしかない) |
K シェル系? | B シェル系 2 のうち ksh 関係? |
A シェル系? | B シェル系 2 のうち ash 関係? 初期の ash は? |
真面目に分類しようとまとまりません。要するに 30年〜40年前に生まれたと思われるこれらの呼び名は、時間が経ちすぎてシェルの世界が代わったために現実に即しておらず、人によってどういう意味で使っているか明確ではなく、勘違いを引き起こしやすい呼び名なのです。したがって現在は次のように呼ぶのが良いと思います。
シェルのカテゴリ | シェル |
---|---|
POSIX シェル | ash 系、bash、ksh、zsh など (/bin/sh としても使われる現在の主流) |
その他のシェル | Bourne シェル、tcsh、fish など(それぞれのシェルの名前で呼ぶ) |
現在のシェルのカテゴリは主流の POSIX シェルのみで、条件は POSIX に準拠しているかどうかです。ash 系は厳密に言うなら初期の ash は POSIX 準拠ではないので含みません(が初期の ash の話をする人はいないと思います)。あとは全てその他のシェルです。Bourne シェルは Stephen R. Bourne が開発した本物の Bourne シェルだけということです。
ここで POSIX シェルのうち ash 系とそれ以外のシェル(bash, ksh, zsh など)を分離したいと思うかもしれません。ash 系は拡張機能が少ないシェルで、どれも同じように動いているように思えるからです。しかし ash 系でありながら bash の拡張機能を取り込みつつある Busybox ash という例外が現れたので、もはや ash 系でひとくくりにすることは出来なくなりました。ash 系のシェルである FreeBSD sh、NetBSD sh、dash、BusyBox ash などはそれぞれ独立してメンテナンスされている別のシェルです。ash 系だからといって全て同じように動くわけではありません。FreeBSD sh と NetBSD sh でさえ小さいながらも違いがあります。
シェルスクリプトの移植性について
現在の主流のシェルは「POSIX シェル」というカテゴリに含まれると書きましたが、全ての POSIX シェルは拡張機能を持っており純粋な POSIX シェル(POSIX で標準化された機能のみを実装しているシェル)というものは存在しません。また POSIX に準拠したシェル同士であっても実装によって動作に違いがあり、完全に同じ動作をするシェルは一つもありません。
ではすべての環境で動作するシェルスクリプトを書くにはどうしたら良いでしょうか? それはすべての環境を考慮し動作テストをするしかありません。すべてのシェルやコマンドの実装に対応して書けば、すべての環境で動くシェルスクリプトになりますが、普通に書いて ash 系のシェルで動けばすべての環境で動くシェルスクリプトになるというようなことはありません。環境依存が激しいコマンド(= POSIX コマンドや UNIX コマンド。理由は実装が多いから)を使えば、そのコマンドの互換性についても気を配らなければいけません。シェルスクリプトですべての環境で動くようにしようと考えるのは、手間の割に通常はメリットがないでしょう。もちろんなんでもシェルスクリプトで書けなどとは言っておらず、必要ならば他の言語も使ってください。適材適所でシェルスクリプトを使ってください。
汎用ツールを作りたいならすべてで動くように頑張る意味があると思います。しかし動作環境が限定される(限定させることができる)なら頑張る必要はありません。そのシェルスクリプトは本当にすべての環境で動くようにする必要があるのでしょうか? 今更古い Bourne シェルや(POSIX 準拠以前の)UNIX System V 環境を考慮して書く必要がありますか? Linux と macOS だけで十分ではありませんか? macOS で動かしたいとしても、新しい bash と GNU CoreUtils をインストールし Linux 風の環境を作れば十分ではありませんか? いくつもある sed や awk の実装に対応するよりも Perl をインストールした方が簡単ではありませんか? 動作環境や対応させるシェルやコマンドの種類が多ければ多いほど対応作業が大変になるのは自明です。実装がいくつもあって対応するのが大変なら、対応する環境やシェル、依存するコマンドを減らせばよいのです。
シェルスクリプトは POSIX で標準化された機能のみを使っていたとしても、すべてで同じように動く保証はないため、本当にすべての環境で動作保証したいのであれば、実際のシェルと環境でテストしなければなりません。ash 系のシェルである FreeBSD sh や NetBSD sh は Linux では動かないので、私は仮想マシンに OS をインストールしてテストしています。(面倒です……)
ただし一つだけ言っておくと、シェルスクリプトの移植性の問題は、現状の問題点であり将来解決可能なものです。シェルスクリプトの移植性が低い理由は OS の機能(POSIX コマンド、UNIX コマンド)を直接利用しているからです。OS の機能を直接使用している場合、OS の違いを自分で意識しなければならなくなってしまいます。シェルスクリプトと OS の間に、OS の違いを吸収する層(移植性問題を解決するためのシェル関数ライブラリや移植性の高いコマンド)を挟み込むことで、シェルスクリプトの移植性の問題は大きく改善が可能です。シェルに関しても OS のシェル (/bin/sh
) ではなく、特定の OS に依存していない(多くの OS で動作する)シェル、例えば bash をインストールして使えば、移植性問題は改善され、どこでも動く上に bash の高度な機能を使うことも可能になります。
POSIX シェル以前のシェル
Thompson shell / PWB shell
UNIX の誕生は 1969 年、Version 1 Unix のリリースは 1971 年です。その Version 1 Unix で使われていた最初のシェルが Thompson shell です。原始的なシェルで変数やフロー制御の機能はシェルには実装されていませんでした。フロー制御の機能が必要になった時、if
や goto
などは外部コマンドとして実装されました。このあたりの歴史は ksh の開発者である David Korn が語っています(参考)。
パイプの機能は 1973 年の Version 3 から追加されました。UNIX が AT&T 以外に配布されたのが 1975 年の Version 6 Unix からで、1979 年の Version 7 Unix では Bourne シェルに置き換えられたので Thompson shell は本当に初期の頃に UNIX を入手した企業や大学でしか使われていないマイナーなシェルということになります、(現在のようにインターネットで簡単にダウンロードできるような時代ではないことに注意してください)
1975 年にリリースされたのが PWB shell です。これは PWB/UNIX と呼ばれる Version 6 Unix をベースとしたプログラマ向けの Unix に含まれていたシェルで Thompson shell 以上にマイナーなシェルです。PWB shell は Thompson shell にいくつかのフロー制御機能と変数(ただし名前は一文字のみ)を追加し、ある程度のプログラミングが可能な形に修正されたものです。しかし制限が大きく、新たに書き直された Bourne シェルに置き換えられることになります。
Thompson shell および PWB shell は Bourne シェルと同じく sh
というプログラム名です。フロー制御の書き方に違いがあるため Bourne シェルとの互換性はないと言って構わないと思いますが、外部コマンドを並べパイプとリダイレクト程度を使うだけの単純なスクリプトであれば Bourne シェルで実行することは可能だったでしょう。これら初期の UNIX で用いられていたシェルのドキュメントは「Origins of the Bourne shell」で読むことが出来ます。
しばしば「シェルスクリプトはプログラミング言語に含まれるのか?」という、プログラミング言語じゃなかったら一体なんなんだよ?と聞き返したくなるような質問を見かけますが、ほとんど最初の頃からプログラミング言語として作られているのは明らかです。シェルスクリプトがプログラミング言語である以上、シェルスクリプトを使いこなすにはプログラミング言語の知識や考え方が必要です。確かに最初の Thompson shell はコマンドを書き並べるだけの機能しかありませんでしたが、すぐに if
や goto
が必要になりシェルに組み込まれていきました(必要ないと考えていたら組み込みません)。PWB shell に関する論文のタイトルは「Using a Command Language as a High-Level Programming Language(コマンド言語を高水準プログラミング言語として使う)」です。そして最初からプログラミング言語としても使えるように再設計され、一から作り直されたのが次の Bourne シェルです。
Bourne シェル
AT&T 以外に広く配布された最初の UNIX が 1979 年にリリースされた Version 7 Unix です。これは UNIX が商用化路線に向かう前の最後の UNIX (Research Unix, Ancient UNIX) でした。Bourne シェルはこの Version 7 Unix とともにリリースされ Thompson shell / PWB shell を置き換えました。Bourne シェルにはバージョン番号はなく UNIX の一部としてリリースされたため UNIX のバージョンで区別されています。最初のバージョンからすべての機能が実装されていたわけではなく、UNIX のリリースとともに以下のような機能が追加や変更が行われました。
UNIX バージョン | 追加機能 |
---|---|
Version 7 (1979) | フロー制御、コマンド置換、シェル変数、環境変数、パラメータ置換 |
System III (1981) | コメント (# )、set -- 、ビルトイン (test /[ ) |
SVR1 (1983) |
shift n |
SVR2 (1984) | シェル関数、ビルトイン (unset , echo , type ) |
SVR3 (1986) | 現在の $@ の動作への仕様変更、ビルトイン (getopts ) |
SVR4.0 (1989) | ジョブコントロール |
SVR4.2 (1992) |
read -r (ShellCheck で必ず指定しろと言われるほど重要な機能) |
Bourne シェルの最終バージョンは POSIX でシェルが標準化された年と同じ 1992 年の SVR4.2 ですが、これを採用した Unix は UnixWare や OpenUnix ぐらいしかなく、主な商用 UNIX で採用された Bourne シェルはそれよりも前のバージョンが使われていました。Bourne シェルの更新履歴から読み取れることは、各バージョン間には小さくない違いがあり互換性はそれほど高くなかったということです。初期の Bourne シェルにはシェル関数はありませんし "$@"
の動作が現在のシェルとは異なっています。更に問題なのは AT&T からライセンスを受けて開発された商用 UNIX です。これらは各 UNIX ベンダーごとにそれぞれ独自の修正を加えており、元になっている Bourne シェルと動作が異なる場合があります。ライセンス上の理由から初期の BSD (3BSD、4.xBSD) を除いて BSD 系 Unix では Bourne シェルは使われていないことに注意してください。皆が言う Bourne シェル とは一体どのバージョン、どの実装の話なのでしょうね?
各 Bourne シェルのバリエーションの違いなどは「The Traditional Bourne Shell Family」にまとめられていますが、すでに過去のシェルで詳しく知る必要はないでしょう。もし仮に今も Bourne シェル を使っているとしたら POSIX シェルへ移行することをおすすめします。Bourne シェルと POSIX シェルの具体的な違いは、簡単にですが「Bourne Shell(レガシー sh)とPOSIXシェル(sh, bash, etc)の違い」でまとめているので参考にしてください。とはいえ移行作業で大変なのはシェルの互換性と言うよりも、むしろコマンドの互換性だと思いますが。
「最新の」Bourne シェルについて
AT&T の公式の Bourne シェルの開発は終了していますが、その後継となるプロジェクトが二つあります。私はこれらのシェルはメンテナが異なるため、世間で一般的に言われている Bourne シェルとは別物として扱っていますが、一応ソースコードを継承した最新 Bourne シェルはありますということで紹介です。
一つは 2005 年にオープンソースとして公開された OpenSolaris を元に開始された The Heirloom Project です。これは伝統的な Unix ユーティリティのコレクションで基本的には POSIX 準拠ではない元の UNIX コマンドのままの動作ですが Unicode (UTF-8) 対応が行われているようです。その中の一つとして The Heirloom Bourne Shell が含まれています。The Heirloom Project は 2007 年を最後にプロジェクトが放棄されていましたが、後継となるプロジェクトが 2021年10月から始動し 2022年8月に Heirloom NG として正式にフォークされたようです。というようなことが英語版 wikipedia の Heirloom Project に書いてあるのですが、関連する情報を調べてもほとんど見つからず、日が浅いプロジェクトであるため今後の継続性の判断は難しい所です。問題なくビルドできれば、古いシェルスクリプトの互換性の理由などから古い UNIX コマンドの最新 OS 対応版が必要な場合に、このプロジェクトのソフトウェアが利用できるかもしれません。元の The Heirloom Project の引き継ぎであれば最新 OS に対応する以外の大きな改良は無く POSIX への準拠は目指さないと思われますが、どうなるのかはよくわかりません。
もう一つは Schily Bourne Shell というシェルです。こちらも同じく OpenSolaris からの派生で、OpenSolaris 版の Bourne シェル (SVR4)をベースに開発が始まりました。「POSIX 準拠ではない Bourne シェル互換の obosh」「最小限の POSIX 準拠の pbosh」「拡張機能を含む POSIX 準拠の bosh」の 3 つの亜種が含まれています。開発者の Jörg Schilling が 2021年10月10日に亡くなったためしばらく停止状態でしたが、プロジェクトの継続を望むボランティアグループに無事引き継がれたようです(新しいプロジェクトページ)。こちらは obosh を除き、従来の Bourne シェルを POSIX 準拠にすることが目的であるため、最新の POSIX へ対応していくと見られます。現在もっとも安心して使える最新の Bourne シェルです。
C シェル (csh, tcsh)
csh は 1979 年にリリースされた 2BSD 用に開発されたシェルです。2BSD は Version 6 Unix をベースとしており Bourne シェルはまだ含まれていませんでした。リリースは 1978 年と Bourne シェルより早くリリースされたことになっていますが、Bourne の開発は 1975 年の終わり頃に開始され 1976 年に最初のバージョンが公開されており、ほぼ同時期に開発が行われています(参考)。プログラミング可能なシェルスクリプトが求められていた当時の AT&T Unix の回答が Bourne シェルで、BSD Unix の回答が C シェルというわけです。
インタラクティブシェルとしての機能が Bourne シェルよりも優れていたため 1990 年代頃(?)までは csh および後継の tcsh がよく使われていましたが、Bourne シェルよりも後のシェルには csh / tcsh 相当の機能が含まれたため次第に使われなくなっていきました。FreeBSD 13 では root のデフォルトシェルは今も /bin/csh
ですが、次期バージョンから /bin/sh
に変更になるようです(参考)。
C シェルは一般的に C 言語に近いと言われていますが、実際には C 言語らしさはほとんどありません。関数はありませんし、if
の終わりは endif
ですし、for
もありません(代わりに foreach
があります)。せいぜい if
の条件文が C 言語のように書ける程度のものです。またシェルスクリプトを書くには適しておらず、有名な「有害な csh プログラミング」にて 1995 年の時点で多くの問題点が指摘されています。昔に使って慣れてしまったという人を除いて C シェルを選ぶ理由はないでしょう。
公平のために言うと tcsh は今も開発が続いており(プロジェクトページ)、最新版の 6.24.01 は 2022年5月12日にリリースされています。最新版では新機能 $'...'
(dollar-single-quotes) も追加されています。これは POSIX シェルでも標準化されると見られている機能と同じで、バックスラッシュエスケープ文字を使うことが出来る文字列クォートです(例えば echo $'FOO\nBAR'
の \n
のような書式で改行文字を使うことが出来ます)。もしかしたらいずれは問題点が根本的に修正され、復活を遂げる日が来るかもしれませんが、現状としては大きな影響力があるシェルではなくなっています。
rc シェル / es シェル
rc シェルは AT&T UNIX の後継となるべく開発されていた Plan 9 用のシェルです。残念ながら Plan 9 の開発が事実上中止となり rc シェルが普及することはありませんでしたが、Bourne シェルに代わり標準シェルとなるはずだったシェルです。ここからわかることは UNIX の開発者たちは Bourne シェルが最高のシェルだとは考えていなかったということです。Bourne シェルおよび後継の POSIX シェルがあのような文法なのは歴史的理由にすぎません。rc シェルの方が優れているのならなぜ POSIX で標準化されなかったのかと問われれば、rc シェルは移植性がある(どこでも使える)シェルではなかったからでしょう。
rc シェルは Bourne シェルよりシンプルな文法を持ったシェルです。私が特に大きな違いだと考える特徴は、すべての変数が単一の値ではなく配列となっていることです。配列は複数の引数を保持するための $@
に相当する変数として必要なものです。また従来のパイプ |
に加えて、線形でないパイプとして cmp <{old} <{new}
のような構文を持っています。これは bash などにあるプロセス置換と同等の機能です。ローカル変数に相当する機能もあります。rc シェルの仕様から実現されるべきだったシェルの未来、シェルが本来持っていなければならなった機能、不要だった機能を読み取ることが出来ます。ただし rc シェルは新しい設計の OS である Plan 9 での利用を前提としているので Linux / Unix で使うには不足している機能もあるようです。
rc シェルのドキュメントは「Rc — The Plan 9 Shell」で読むことが出来ます。(注意 このドキュメントでは Bourne シェルとの比較がされていますが、これは必ずしも POSIX シェルに当てはまるわけではありません。例えば IFS に関係するセキュリティホールの話は POSIX シェルには当てはまりません。)
Plan 9 は 2000 年にオープンソースとなりましたが、それ以前はソースコードは一般に公開されていませんでした。そこで 1991 年にオープンソースとして再実装したのが Rakitzis rc です。いくつか機能追加も行われているようですが、Plan 9 rc と大きな違いはないように思います。
そして Rakitzis rc を改良したのが 1993 年にリリースされた es - extensible shell(非公式) です。開発者の一人は Byron Rakitzis です。Rakitzis rc の後継というより、新たに関数型プログラミング概念を取り入れて拡張した派生シェルと考えるのが良さそうです。Rakitzis rc の方は Byron Rakitzis 本人によって頻繁ではないもののメンテナンスされているようですが、理由はわかりませんが es の方は第三者 (James Haggerty) によってメンテナンスされているようです。
POSIX 標準規格の役割
Bourne シェルの時代を過ぎ、シェルの仕様は POSIX で標準化されることになります。POSIX シェルの話をする前に、理解を助けるために、なぜ POSIX 標準規格があるのか、その役割を説明したいと思います。POSIX の話はいいやという人は飛ばしてください。
POSIX とは「OS のインターフェース」だけを標準化したもの
UNIX の OS のインターフェースが POSIX として標準化されたのは 1988 年です。ただし、この時に標準化されたのは POSIX API(C 言語インターフェース)や各種ファイル形式などで、この時点ではまだシェルは標準化されませんでした。この標準規格は POSIX.1 と呼ばれました。POSIX でシェルとコマンドが標準化されたのは少し遅れて 1992 年です。この標準規格は POSIX.2 と呼ばれました。現在では POSIX.2 は POSIX.1 に吸収されており、POSIX の標準規格は POSIX.1 のみとなっています。最新の POSIX は POSIX.1-2008 (Issue 7) に細かなバグ修正を加えた POSIX.1-2017 です。次の改訂 POSIX.1-202x (Issue 8) は近いうちに行われる予定です。
私は以前は OS といえばカーネル相当の部分(POSIX API 相当)が主で、コマンドはおまけでついている便利ソフトウェア程度にしか考えていたのですが、そうではなく UNIX に含まれる全てが OS であり、シェルや標準的なコマンドも OS の一部として扱われています。POSIX の標準化の対象は「OS のインターフェース」だけであることに注意してください。OS をどのように実装するかは決められていませんし、アプリケーションの領域は標準化の対象外です。UNIX の機能のうち「移植性があるアプリケーションを開発するのに必要」な OS のインターフェースのみを標準化しており、UNIX のすべての機能の標準化を目指したものではありません。OS によって違う部分は OS の機能だけなので、そのインターフェースさえ標準化されていれば、移植性のあるアプリケーションを開発すること可能だという理屈です。
POSIX は「POSIX で指定した言語やシェルやコマンドのみを使って開発しなければ POSIX 準拠のソフトウェアにはならない」とは言っていないことに注意してください。あくまで OS の機能を使う時は、POSIX の標準規格に従うと移植性のあるアプリケーションを開発しやすいと言っているだけです。OS はそれぞれ別の会社・団体が開発しているので OS の機能にはどうしても違いがでてしまいます。違いがある部分は最小限にしなければいけません。なんでもかんでも OS のインターフェースにする必要はありません。例えば、さまざまな言語やライブラリやコマンドを OS のインターフェースにする必要はありませんよね? OS のインターフェースにしてしまえば、それぞれの OS ベンダーがそれぞれで開発することにつながってしまいます。それでは OS 間の互換性は低くなってしまいます。可能な限り OS の部分は減らしたほうが良いのです。
実際の所 OS のインターフェースには(直接)依存しない方が移植性は高くなります。なぜならそのソフトウェア(言語やライブラリやコマンド)を作っている会社・団体が一つないし少数しかないからです。実装が多ければ多いほど実装による違いが発生します。仮に実装が一つしかないのであれば、その実装に違いがあることはありません(実際にはバージョンによる違いがありますが)。どこでも同じように動くように作られた言語やライブラリやコマンドというのは OS の細かい違いを吸収しています。これは OS がハードウェアの違いを吸収しているのと同じ構図です。OS はハードウェアの違いを吸収していますが、OS の違いは残っています。そのためそれを吸収するソフトウェアが必要です。それが「どこでも動くように作られたソフトウェア」であり、どこでも動くソフトウェアに依存して作られたアプリケーションはどこでも同じように動きます。しかし OS 上でソフトウェアを動かす以上 OS の機能を使わないわけには行かないので、最小限の OS のインターフェースを標準化し、動作違う所があればそれを明確にして、移植性があるアプリケーションを開発しやすくしたのが POSIX なのです。逆に言えば高度な機能は OS の外で実現すれば十分であるということです。
少し余談になりますが次世代 UNIX として開発が進められていた Plan 9 は UNIX を分散 OS に進化させる試みでした。分散 OS とはネットワークでつながった複数のコンピュータを、一つのシステムとして見えるようにする OS で、アプリケーションからはネットワークの向こう側のコンピュータをローカルコンピュータのように使うことができる OS です。しかし歴史の流れは OS をこのように拡張するのではなく「高度な機能は OS の外で実現しましょう」を選びました。それが現在のクラウドやコンテナオーケストレーションシステムです。分散 OS によって作られた世界も見てみたい気はしますが、現実としては人々は OS を大きく変えるよりも小さな変化を選んだということです。それほどまでに互換性というのは罪深い縛りなのです。
話を戻すと「最小限の OS のインターフェースのみを標準化する」という POSIX の方針は「高度な機能は OS の外で実現すれば良い」という考え方であり「POSIX コマンド(OS の基本機能)だけを使って開発しよう」という考え方とは正反対のものであることを意味しています。そもそも OS を管理するシステムコマンド(ユーザー管理やパッケージ管理ツールなど)は POSIX の対象外の領域を扱うコマンドで OS 依存なので移植性はありません。すなわち POSIX で標準化されたコマンドだけでなんでも出来るわけがないのです。また、最小限の OS の機能だけを使う場合、それ以外の全てを自分で作らなければならなくなってしまい生産性は大きく下がってしまいます。現実には POSIX コマンドでさえ標準インストールされているとは限らないので、足りないコマンドはインストールしなければなりません。何かしらインストールするのであれば POSIX コマンド以外をインストールするのも同じ事です。
私が言いたいのは多数の「どこでも動くコマンド」を POSIX コマンドではないからと言って無視するのはやめましょうということです。なぜ標準インストールされているコマンドだけで頑張ろうとするのか理解できませんが、どこでも動くコマンドが生まれたのは、移植性があるアプリケーションを開発を容易にした POSIX の成果です。POSIX のおかげでどこでも動くコマンドが生まれたのに、それを使わないというのは POSIX の存在価値を否定するものです。オープンソースでどこでも動くコマンドがすでにあるのに、それと同じものを OS ベンダーが再実装したりフォークして自分たちでメンテナンスしていく理由はないので、それらのコマンドが OS のインターフェースとなることもありません。POSIX コマンドに画期的で便利な新しいコマンドが追加されたり、大きな改善が行われることは今後もずっとありません。行われるのは小規模な改善のみです。もし POSIX コマンドだけを使おうとするのであれば、この先ずっと便利なコマンドが使えないということです。
POSIX 標準化以前に多くのシェルが誕生した理由
現在 POSIX シェルとなっているシェルの多くは、POSIX でシェルが標準化されるよりも前に開発が始まりました。それらのシェルは Bourne シェル または ksh88 のクローンとして開発が始まりました。いくつものクローンが誕生した理由は、当時の Bourne シェル、および ksh88 はプロプライエタリなソフトウェアだったからです。AT&T から正式なライセンスが与えられた UNIX でなければ使うことが出来ないシェルでした。初期の 4.3 BSD (1986) で Bourne シェルが使われたこともありますが、1980 年代後半から始まる UNIX の標準規格を巡る争いである UNIX 戦争 によって、BSD Unix で Bourne シェルを使うことはできなくなりました。事実上 Bourne シェルは商用 UNIX だけで使われていたシェルだったということです。
Bourne シェルが自由に使えないという制約からさまざまなシェルが誕生しました。AT&T でもまたインタラクティブシェルとして使い勝手がよく、強化されたスクリプト言語が欲しいユーザーのために、後継の KornShell (ksh) が誕生しました。Bourne シェルまたは ksh を採用した商用 UNIX でも、それぞれ会社がそれぞれシェルに手を加えており、今で言うフォークが行われていました。上流のプロジェクト(つまり AT&T)がシェルのソースコードを一元管理するのではなく、各 UNIX ベンダーが既存の UNIX のソースコードをベースとして使い、そこから自分たちの手で自分たちのコンピュータ用に UNIX を移植するというやり方は、さまざまなアーキテクチャのコンピュータがありオープンソースという考え方がない当時の文化です。そのため Unix は SystemV 系、BSD 系ともに、同じ名前でありながら、それぞれ異なる動きをするシェルがいくつも誕生しました。そしてシェルスクリプトの移植性は低くなって行くことになります。
なぜ POSIX はシェルスクリプトの言語を改善しないのか?
残念ながらそれは POSIX の仕事ではありません。POSIX が改善しないのではなく、各 OS ベンダーやシェル開発者がシェルを改善していないのです。実際には改善してないのは主に BSD 系や商用 UNIX のシェルと dash で、それ以外の bash や zsh などは改善し続けています。改善し続けていますが、複数のシェルで移植性がない限り POSIX で標準化することはできません。
例えば echo
コマンドは複数の実装があり、それぞれの実装で細かい動きが異なります。もし全てのシェルで echo
コマンドが全く同じ動きをするように変わったのであれば、POSIX もそれに合わせて改訂されるでしょう。しかし OS ベンダーやシェル開発者は互換性を維持するために既存の動作を変えません。したがって POSIX もそれを前提に標準化するしかありません。POSIX は仕様を意図的に明確に指定していない部分がありますが、それは各 OS が互換性を保てるようにあえて「どの実装でも OK」としているからです。例えば echo
コマンドで -n
を指定した時、その動きが出力の最後に改行しないを意味するのか、そのまま -n
という文字列で出力するのかは POSIX では指定されていません。これは「動作を指定しない」=「どの実装でも OK」と定義することで、互換性を保つことを可能にしつつ echo
コマンドの標準化を可能にするためのテクニックです。
POSIX は実装によって動作に違いがあることを許容している標準規格です。これは標準規格に反して良いという意味ではありませあん。標準化する前から違っていたのなら違ったままで良いという意味です。動作の違いを許容している部分は「POSIX では動作を指定しない」のように明確に書かれています。これらの動作の違いに対応するのはアプリケーション開発者の仕事です。POSIX のスタンスは「POSIX 標準規格書に動作が指定されていないとちゃんと書いてあるんだから、POSIX 読めば実装によって動きが違うということはわかるでしょ?」なのです。アプリケーション開発者は「POSIX で標準化されているコマンドだから同じ動きをするはず」ではなく「POSIX に移植性を高めるためのガイドラインがあるからそれをよく読んで開発しよう」と考えなければなりません。
動きを統一するのではなく既存の動きを明文化するのが POSIX です。異なる OS との移植性があると認められれば、それは POSIX に後から追加されるでしょう。移植性があることがポイントの一つなので Linux だけを変えてもダメです(Linux だけが使える機能が、他の OS との移植性がないのは当たり前でしょう?)。POSIX は古いからそれに変わる新しい OS の標準規格が必要だとか言っている人がいますが、新しい標準規格を作ったとしてもメリットがなければ採用しません。新しい標準規格に従うのが Linux だけだったとしたら、そこにどのような意味があるのでしょうか? 少なくともそれは移植性のない OS の標準規格です。
POSIX が長い間、OS の標準規格であり続けた理由は、OS の仕様を決めるのは OS ベンダーに任せて、可能な限り既存の実装の動きを明文化することに徹したからです。OS ベンダーが決めることに余計な口出しをしなかったからこそ OS ベンダーは POSIX に準拠しようと考えたのです。それにたとえ POSIX が何かを決めたとしてもそれに従う必要はありません。GNU は POSIX などの「標準規格を命令ではなく提案とみなす」と明確に述べています。GNU プロジェクトの創始者で POSIX という名前の考案者でもあるリチャード・ストールマンによる「POSIX とはなにか?」を説明した以下の記事も参照してください。
シェルスクリプトの言語も同じです。POSIX が先導してシェルの仕様を作るのではありません。シェルの仕様を決めるのは OS ベンダーやシェル開発者です。POSIX は標準化が可能な所を見つけてそれを文書化するだけです。したがって POSIX シェルの仕様を変えてほしいのであれば、先に bash なり ksh なり zsh なりの複数のシェルの開発者に提案して変えてもらうしかありません。しかし互換性の問題やら、それをやる意味はなにか?と問われ採用されるのは難しいでしょう。だからといって POSIX に働きかけて標準規格を変更することで、各 OS ベンダーやシェル実装者に POSIX 準拠しろと変更を迫る試みは成功しません。順番が逆なのです。そういうわけで POSIX は大きな改訂はされませんし、改訂にも時間がかかってしまうのは仕方ありません。POSIX が改訂してないのではなく、OS ベンダーが改訂してないのです。
POSIX シェルへの新しい機能の提案が却下される流れを二つ紹介しておきます。最後のコメントを読んでみてください。どんなに素晴らしい提案でも先に複数のシェルに実装されていなければ却下されてしまうのです。
- 0000553: Add new type of shell and shell feature requirement for new shell in posix.
-
0001026: The shell should support access to all 32 bit from the exit code
- Schily Bourne Shell 開発者による提案
/bin/sh はもはや Bourne シェルではない
POSIX で標準化されているシェルは sh という名前ですが、これは Bourne シェルという意味ではありません。Bourne シェルは POSIX に準拠していないため、商用 UNIX では Bourne シェルから POSIX シェルへの移行が必要になりました。しかしすでに多くの資産があった商用 UNIX では、そう簡単に Bourne シェルを POSIX シェルに置き換えることは出来ません。POSIX シェルのプログラム名は sh
ですが、これは /bin/sh
という意味ではありません。実は POSIX では sh
があるディレクトリはどこでも良い、というかディレクトリ構造は /dev/null
や /tmp
など一部のパスを除いて決まっていません。これは OS の互換性を保つという点で意味があります。つまり /bin/sh
を Bourne シェルとして残したまま、別のディレクトリに POSIX シェルを置いたとしても POSIX 準拠の OS になるということです。
シェルの動作を POSIX が定めた仕様に変更することは、既存のシェルとの互換性がなくなることを意味しています。POSIX に準拠させたために OS をアップグレードした時に既存のシェルスクリプトが動かなくなってしまえば本末転倒です。OS を壊すような標準規格であれば OS ベンダーはその標準規格を採用しようとしたりしません。POSIX もそのこと十分理解しているから、POSIX は互換性を保てるように配慮し、過去の実装からの変更を最小限に保つこと、新しいものを作り出すのではなく歴史的な慣習を明文化することを方針としているのです。
一例を上げると Solaris 10 (2005) では /bin/sh
は Bourne シェルであり /usr/xpg4/bin/sh
に POSIX シェルがあります。POSIX 認証を受けた(古い)UNIX はこのようにして互換性を保ちながら POSIX に準拠させています。このような OS の実装は互換性の観点から妥当だと思いますが、これが「/bin/sh
は Bourne シェルである」という言葉が長く残っており、多くの人を勘違いさせている原因の一つではないかと考えています。
Solaris 11 (2011) では /bin/sh
は ksh93 に置き換わりました(参照)。AIX などその他の商用 UNIX でも同様です(参照)。今や「/bin/sh
は Bourne シェルではなく POSIX シェルのどれか」です。そして Bourne シェルが使えなかった Linux、FreeBSD、macOS では /bin/sh
が Bourne シェルだったことはありません。
POSIX は拡張機能の実装を妨げない
POSIX の重要な方針の一つは「拡張機能の実装をさまたげないこと」です。例えば bash、ksh93、zsh と言ったシェルはPOSIX シェルで規定されていない多くの拡張機能を持っています。「拡張機能は標準規格に違反してる。勝手に拡張するなんて許せんシェルだ。GNU も勝手に拡張するな。」のように考えている人がいるようですが、これは POSIX の方針からすれば間違った考え方であり、POSIX 標準規格というものを理解していません。そもそも POSIX の方が後から策定されており最初からシェルに実装されていた機能もあるわけで「標準を拡張した機能」と呼ぶのもおかしな話です。
POSIX は改訂されない標準規格ではありません。すでに説明しましたが近いうちに POSIX.1-202x (Issue 8) に改訂されます。前回の大きな改訂は POSIX.1-2008 (Issue 7) で、今回は 14 年ぶりとちょっと長くなっただけです。互換性を保つことが出来ない変更は基本的にありません。というよりも、どの OS も互換性を保って開発されており、POSIX はそれを明文化しているだけなので互換性が保たれているのは当然です。(ただし POSIX は ANSI C を規格の一部として採用しているために、ANSI C で大きな変更があった場合には、互換性が保てない場合があるようです)
POSIX の改訂というのは最新の実装を標準規格に取り入れることです。ではこの最新の実装とはなんのことでしょうか?それは現在の拡張機能のことです。POSIX が先導して新しい機能を作ることは(基本的に)ありません。既存の多くの実装に取り入れられた「移植性が高いと認められる拡張機能」を標準規格に取り入れるという方針である以上、拡張機能を実装することを認めなければ、POSIX の標準規格を改善することができず、いつまでも 30 年も前の仕様で固定化されることになってしまいます。したがって拡張機能を実装したとしても POSIX 違反にはならないように文章に気を配って標準規格が策定されています。もし実装された拡張機能が POSIX 違反になっている場合、逆に「POSIX が拡張を妨げているバグ」として POSIX 標準規格の方が修正されることすらあります。もちろんシェルに関しても「拡張機能を実装したシェルも POSIX 準拠である」と認めています。
POSIX に準拠した、bash、ksh、zsh などから移植性が高い拡張機能(多くのシェルで同じように使える機能)から、次の POSIX シェルの標準規格が作られます。 POSIX.1-202x (Issue 8) では例えば set -o pipefail
が追加されます。pipefail の追加が POSIX に提案されたのは 2013年11月です。これは ksh93g (1998) で最初に実装され、bash、zsh など多くのシェルでも実装されましたが、古い ksh88 や現時点での最新の dash (0.5.11.5) には実装されておらず、FreeBSD sh では 2019年7月9日 リリースの 11.3 で実装された機能です。ash 系は伝統的に拡張機能を実装しない傾向にありますし、拡張機能が全てのシェルで実装されることはまずありませんが、代表的なシェルで実装されたものが新しい標準規格になっていく、そして実装されていないシェルを引っ張っていくという流れであるということがわかると思います。POSIX を改訂するために拡張機能は重要な存在なのです。
POSIX シェルとその成り立ち
ash 系 (sh, dash, Busybox ash)
ash 系のシェルは一般的に純粋な POSIX シェルに近いシェルで拡張機能をあまり持っていません。標準規格に近いシェルと言えば聞こえはいいですが、POSIX シェルの発展を妨げている低機能なシェルと言えるかもしれません。もっとも POSIX もそれはわかっているので、ash 系のシェルで実装されていないことを理由に、拡張機能を取り入れないとは考えてなさそうですが。
ash は元々 UNIX 戦争で Bourne シェルが使えくなった BSD Unix のために開発されました。系統図では最初の ash から POSIX Family Shells に含めていますが吹き出しに書いたように、最初の ash は POSIX 準拠ではありません。Bourne シェルのクローンとして開発されたため(多少の違いはありますが)元々は Bourne シェル相当の機能しか持っていませんでした。これが ash 系のシェルに拡張機能が少ない理由の一つでしょう。
ash は 1991 年に 4.xBSD に採用された後、1992 年に POSIX シェルが標準化された頃から POSIX.2 への準拠作業が開始されます。ash 系シェルの変更履歴(Ash (Almquist Shell) Variants)を見てみると、この作業には結構時間がかかったようで 2000 年を過ぎてもまだ POSIX.2 準拠への修正が入っているようです。その間、1993 年に NetBSD sh として採用され、1996 年に FreeBSD sh として採用され、1997 年に Debian Linux へ dash として移植され、2001 年に dash が Busybox ash として採用されと、いくつものフォークが誕生しています。
FreeBSD sh や NetBSD sh は基本的に POSIX への準拠作業を行っただけなので、ほとんど同じ動きをしていますが、それぞれの OS で修正を加えているために厳密には OS 固有(その OS でしか使えない)のシェルです。おそらく Linux 移植版はないと思います。例えば FreeBSD 13 の sh にはない POSIX モード (set -o posix
) が NetBSD sh にはあります。次期 POSIX Issue 8 で標準化される可能性が高い Dollar-Single Quotes(議論中)や case
のフォールスルー ;&
(ドラフト)が先行実装されていたり、if
の中を空にしてもエラーにならないという POSIX 準拠ではない動作もあるので完全に同じだとは思わないほうが良いでしょう。ちなみにこの問題は POSIX モードの NetBSD sh や dash ではエラーになるように修正されています。
# FreeBSD sh や NetBSD sh ではこれが動いてしまう
if true; then
# POSIX 準拠であるなら、ここを空(またはコメントのみ)にはできない
fi
# NetBSD sh の POSIX モードならエラーになる
set -o posix
if true; then
: # POSIX 準拠なら何かしらのコードが必要(: は何もしないコマンド)
fi
# 参考 Bourne シェル、dash、bash、ksh、mksh ではデフォルトでエラーになる
# yash は POSIX モードでエラーになり、zsh ではエラーにならない
dash は NetBSD sh からフォークしたシェルです。基本的に FreeBSD sh や NetBSD sh と同じような動きをしていますが次期 POSIX Issue 8 で標準化される機能はまだ未実装のようです。まだドラフト段階なので実装していなくても不思議ではありませんが、あまり最新の POSIX への準拠を急いでいないような気もします(急ぐようなものでもありませんし)。
BusyBox ash は元は dash からのフォークですが最近は bash の拡張機能を取り込んでおり、ash 系の中でもっとも高機能なシェルとなっています。bash の拡張機能というのは [[ ]]
や拡張された変数展開(${VAR//a/b}
など)やプロセス置換(<(cmd)
)といった他のシェルでも実装されている機能だけではなく、シェル変数 BASH_XTRACEFD
と言った明らかに bash の機能でしかないものまで取り込んでいます。これは BusyBox が現実のシェルスクリプトを実行できる環境を作ることを目指しているからです。現実として bash スクリプトは多く使われているわけで、より多くのシェルスクリプトを動かすには bash 相当の機能を備えていなければならないというのは BusyBox の考え方としては至極当然です。BusyBox ash のソースコードのコメントを読むと bash との互換性を意識していることがはっきりとわかります。
ksh 系 (ksh88, ksh93, ksh93u+m)
ksh は UNIX を開発した AT&T で Bourne シェルの後継として開発されたシェルです。Bourne シェルのソースコードを元に開発が始まっているようです。UNIX System V R4 に含まれたため主に商用 UNIX で広く使われています。ksh88 はそうでもありませんが、ksh93 はスクリプト言語 (awk, perl) をかなり意識しており多くの拡張機能を持ったシェルです。過去にはグラフィカルユーザーインターフェースを作るのにも使われていたようで、オブジェクト指向プログラミング的なことまでできるため bash よりもはるかに拡張機能が多い(スクリプト言語としての機能なら zsh を超えている)と言って構わないレベルなのですが、その実力は広く知られていないように感じます(個人的にはシェルスクリプトでそこまでやる必要はないと思っているので ksh93 の実力はシェルとしては過剰に思います)。ksh の詳しい歴史などについては開発者である David Korn 本人が 1994 年の論文でまとめているので「ksh - An Extensible High Level Language」(PDF) を参照してください。また公式サイトのドキュメントにも多くの文書が公開されています。
KornShell FAQ によると KornShell は正確にはスクリプト言語の名前で、その実装が ksh です。スクリプト言語自体はパブリックドメインで誰でも再実装可能となっていましたが ksh 自体はプロプライエタリなソフトウェアでした(後にオープンソース化されます)。最初のバージョンは 1983 年とされています。1986 年に ksh86 が限定的に公開されたようですが、広く使われたのは ksh88 です。この ksh88 をベースに POSIX シェルの標準規格が策定されました。POSIX は Bourne シェルだけではなく ksh88 とも一部互換性がない仕様で標準規格を策定したのでオリジナルの ksh88 は POSIX に準拠していません。ただし UNIX ベンダーは ksh88 を修正して POSIX シェルとして利用していたようです(詳細は調べていません)。他のシェルと同じく ksh88 も細かいバージョンアップを繰り返しており、88c、88d、88e のように後ろにアルファベットが追加されます。ksh88 の最終版はおそらく 1993 年にリリースされた 88i です。古い ksh のバージョンに関しては Some ksh versions available を参照すると良いでしょう。
ksh88 の後継である ksh93 は POSIX 準拠を目指し、一部 ksh88 とは完全な互換性を保たずに作られた高機能なシェルです。ksh93 は ksh88 から引き続きプロプライエタリなソフトウェアでした。そのため BSD Unix では主に ash 系のシェルが、Linux では bash が広く使われるようになりました。ksh93 のソースコードは 2000 年に公開されましたが、オープンソースとなったのは 2005 年の ksh93q からです。そのため 2005 年を過ぎた頃からようやく Linux などで ksh がパッケージとして追加されるようになったようです。しかしすでに世の中は Linux の時代となっており bash が圧倒的なシェアを占めており、結局 ksh93 は商用 UNIX 以外ではあまり使われていないように思います。
ksh93 は本当に高機能なシェルで、awk だけではなく(初期の)Perl と張り合えるレベルのスクリプト言語としての機能を備えています。レキシカル変数 my
やオブジェクト指向に対応した Perl 5.0 の登場が 1994 年でその当時の比較であることに注意してください。先の論文の内容から awk (や外部コマンド)の呼び出しで遅くなるという問題に対処するために機能を追加しているということがわかります。bash の拡張機能を嫌うものに、よく bashism と槍玉に挙げられていますが、その元をたどると実は多くの bash の拡張機能は ksh88 または ksh93 がルーツです。ksh が UNIX を開発した AT&T のシェルであることを踏まえると「bashism は UNIX シェルが最初から備えていた公式機能」であり、後からやってきた POSIX シェルが標準化してないからと言って bashism を非難することはお門違いもいいところです。配列、プロセス置換といった機能が UNIX の次である Plan 9 の rc シェルで導入されていたことを考えると、これらの機能は忌み嫌う bashism ではなくシェルの未来の機能の先取りであり、POSIX シェルが 20 年以上前のシェルに未だ追いついていないだけです。
ksh93 は高機能なシェルでしたがバグもかなり多いシェルでした。各 UNIX ベンダーはそれぞれパッチを当てて使っていたようです。AT&T 公式の ksh93 は 2012 年の ksh93u+ が最終バージョンです。David Korn は AT&T を去りましたが、その後も開発版である ksh93v- の開発は 2014 年まで続けられました。その後しばらく放置され 2017 年からコミュニティの手によって開発が再開され 2019 年の秋にksh2020 がリリースされたのですが互換性の問題が発生し放棄されました。その後別の開発者 (Martijn Dekker) の手によって 2020年5月から ksh93u+ から再始動し、それまでの多くパッチを適用し、およそ 1000 のバグを修正し、ksh93u+ がリリースされた 2012年8月1日のちょうど 10 年目である 2022年8月1日に ksh93u+m 1.0.0 がリリースされました。バージョン番号の表記が変わっており ksh93u+m(m は modified の意味?)の部分は固定で、1.0.0 の部分が新しいバージョン番号です。なおバグの修正は続けられており現時点での最新版は 1.0.4 です。まだしばらく細かいバグ修正は行われるのではないかと思いますが、それまでの多数のバグは修正され最も安定した ksh93 のバージョンとなっており、次期 Debian などでも採用されるようです。
POSIX が改定される前に ksh93u+m がリリースできたのは良いタイミングだと思います。ksh93 を採用していた商用 UNIX がより安定した ksh93u+m に置き換えることが可能になったからです。またオープンソース版であり、もちろん Linux でも ksh93u+m は動作します。つまりこれは UNIX から Linux への移行の際にシェルを変更しなくて良くなったことを意味しています。どこかで ksh スクリプトから bash スクリプトへの変換をするお仕事を見かけましたが、ここだけの話、そんな事しなくても良いのです。もっとも外部コマンドの互換性は別の話(おそらくそっちがメイン)ですし、今後のためにも bash スクリプトに変換しておきたいという理由もわかるので意味のない作業だとは思いませんが。
ksh93u+m は名前から推測できるように、大きな機能追加は行われず基本的に ksh93u+ 相当の機能を維持し続けます。ただし ksh93 当初からの目標である POSIX 準拠を実現するのに必要な POSIX モード(set -o posix
) の実装や printf -v
と言った新機能もわずかに追加されています。
pdksh 系 (OpenBSD ksh, mksh)
pdksh は Public Domain ksh の略で、その名の通り KornShell のパブリックドメイン版です。ksh88 がプロプライエタリであったために ksh88 の再実装(一部 ksh93 の機能の取り込み)を行ったシェルで、ksh88 とソースコードは共有していません。ksh88 との互換性があるということになっていますが完全ではありません。ただし配列などの機能は実装されています。パブリックドメインであるため Linux でも比較的早いうちにパッケージが追加されたようです。オリジナルの pdksh の開発は 2000 年頃まで続いています。2000 年というのは ksh93 のソースコードが公開された年で、これを期に開発をやめたように思えます。Debian では pdksh から後述の mksh に置き換えられています。
OpenBSD では /bin/sh
として、また OpenBSD ksh として pdksh を採用(フォーク)しています。OpenBSD では ksh
という名前で pdksh を実行できますが、AIX や Solaris などの AT&T の本物の ksh とは異なる実装なので間違えないようにしてください。OpenBSD の一部であるため pdksh とは異なり今もメンテナンスが続いています。OpenBSD ksh は pdksh および ksh88 相当の機能が維持され続けているようです。また loksh や oksh として Linux など他の OS への移植版があります。
mksh (The MirBSD Korn Shell) は 2002 年に OpenBSD 3.1 からフォークした MirOS BSD のためのシェルですが、多くの OS に移植されています。Linux でも動作しますし、Android でもシステムシェルとして採用されているようでがパスは /system/bin/sh
です。OpenBSD ksh から引き続き、pdksh 系統のシェルですが、それなりに拡張機能が実装されています。拡張は bash や ksh を参考にしていると思いますが少々独自っぽいところもある感じです。こちらもメンテナンスが続いています。
yash
yash は祖先となるシェルを持たず、世界で最も正確な POSIX 規格準拠を目指して開発されたシェルです。 現在の 2.x 系は 2008 年頃から開発されており、開発者は日本人の @magicant さんです。POSIX 準拠ですがそれは拡張機能を持たないという意味ではありません。デフォルトでは配列や [[ ... ]]
と言ったよく知られた拡張機能が実装されています。
yash の特徴的な機能は、他の POSIX シェルとは異なり POSIX モードを有効にすると拡張機能が無効になるということです。現時点ではまだ未リリースですが、次のバージョン 2.54 ではこれが更に押し進み、POSIX で規定されているが動作が指定されていないコマンド(例 local
コマンド)を使うとエラーになる予定です。つまりこれは yash の POSIX モードを使えば、POSIX で規定されていないシェルの機能を使っているかどうかを調べることができ、yash で動けば他のシェルでも動くようになる可能性が高いということです。ただし過信はしないでください。外部コマンドの移植性に関してはシェルの対象外ですし、シェルの機能であってもシェルによって動作に違いがありますし、シェルオプションの拡張機能などは無効にならないようです。
余談ですが現在 yash は Rust での再実装 が行われているようです。正式リリースされる時を楽しみに待っています。
bash
説明の必要もないぐらい有名な、現在最も使用されているシェルです。多くの Linux のユーザーシェルとして使われ、RedHat 系 Linux や macOS (最新版 macOS 13 を含む)などでは /bin/sh
としても使われています。拡張機能が多い ksh93 からシェルとして備えているべき機能を厳選して実装している感じでバランスが取れたシェルです。
他の多くのシェルと同じで開発が始まったのは POSIX 標準化前です。POSIX が標準化の際に仕様を変更したため、初期の bash は POSIX に準拠していませんでした。過去の bash のバージョンとの互換性と POSIX 準拠を両立するために、POSIX モードを導入し /bin/sh
として実行された場合は自動的に POSIX モードとなるようになっています。重要な点として bash は POSIX モードを有効にしたとしてもほとんどの拡張機能は無効になりません。bash は POSIX での指定とは異なる動きを POSIX 準拠にするだけであって、POSIX が認めている拡張機能は POSIX 違反ではないからです。拡張機能を使うと当然、それを実装してない dash などでは動きませんが、どのシェルに対応するかは要件次第であり、動かないなら非対応というだけで良い話です。重要なのはどのシェルに対応するのか要件を知ることと、どのシェル向けのシェルスクリプトを書いているかを自分で把握しておくことです。
最新版の bash 5.2 が 2022年9月26日にリリースされたことからもわかるように、今もメンテナンスが続いており機能強化も行われています。GNU プロジェクトで開発されているシェルなので将来性などについても安心できますし、BSD 系のシェルのように OS 固有のシェルではなく(おそらく)どの OS にでもインストールして動かすことができる移植性が高いシェルであるため、どこでも動くシェルスクリプトを実行するためのシェルとして適しています。
zsh
昔から知る人ぞ知るといった感じで強力なシェルとして有名でしたが、macOS のデフォルトのユーザーシェルとして使われるようになったため一気にメジャーになった気がします。一つ注意ですが zsh になったのはデフォルトのユーザーシェルであり macOS の /bin/sh
の実体は今も bash なので勘違いしないようにしてください。また bash を使い続けたい人は Homebrew などで最新の bash をインストールすることをおすすめします。もちろんユーザーシェルは新しくインストールした bash のパスを指定してください。
zsh は元々は ksh88 と csh (tcsh) をあわせたようなシェルが目標であり、デフォルトでは POSIX には準拠していませんが、sh、bash、ksh、csh の各シェルの動作をエミュレートする設定を多く持っており emulate
コマンドでそれらのシェルの動作に切り替える事ができます。ただし csh のエミュレート機能はあまり良くないと思います。ksh と同様に拡張機能が多いシェルですが、ksh は言語の文法に拡張機能が多いのに対して、zsh はモジュールや変数展開の機能が多いというように拡張機能の方法性が違うように感じます。
zsh の最新版は 5.9 で 2022年5月14日にリリースされています。こちらも積極的にメンテナンスが続いており機能強化もされ続けています。
非 POSIX シェル
昔は Bourne シェルと C シェルしかなかった UNIX シェルも、今日では多くのシェルがあります。それらの多くは独自の目標を掲げており、あえて POSIX に準拠していません。私は使っていないので詳しい解説は出来ませんが、インタラクティブシェルとして、シェルスクリプトとして、より優れたシェルを目指して開発が行われています。
比較的早い段階に開発され知名度も高い fish は 2005 年に開発が始まっています。xonsh、ion、elvish、nushell といったシェルは 2010 年代中半から後半にかけて開発が始まっています。他にも有名なシェルとして PowerShell があります。こちらは Windows 文化からの輸出で UNIX シェルの考え方とは大きく異なっていますが、Linux や macOS でも使うことが出来ます。
シェルの未来
シェルは自由に選ぶことが出来る
シェルの起源は UNIX ではなく CTSS と呼ばれるタイムシェアリングシステムの RUNCOM (1963) にあると言われています。シェルという用語もこの時に生まれました。CTSS のシェルは UNIX シェルとは異なり、カーネルの一部だったようです。UNIX シェルの大きな改善は、それが一般的なユーザープログラムとして実装されるように分離されたことです。このおかげで Thompson shell から始まり、Bourne シェルや C シェルなどを経て多くのシェルが誕生し、自分の好きなシェルを使うことが出来るようになりました。これは大きな改善です。
私は個人的な理由で POSIX シェルと互換性のあるシェルに注力していますが、POSIX シェル以外を否定しているわけではありません。時間的な理由で使っていませんが、一般的に新しいシェルの方が優れているだろうと思いますし、UNIX がシェルをカーネルから分離したのは、シェルを誰でも自由に開発することを可能にするためなわけで、そのような新しいシェルを作っていくことこそが UNIX 開発者の考えでもあると思います。
シェルは誰でも自分の好きなシェルに変更してそれを使うことが出来ます。ここで知っておいて欲しいことは、シェルを変えたからと言ってシェルスクリプトまで変える必要はないということです。例えば macOS では 2019年10月の 10.15 (Catalina) からデフォルトのユーザーシェルが zsh に変わりましたが、シェルスクリプトも zsh で書かなければならなくなったと勘違いしていないでしょうか?ターミナル、つまり黒い画面で使うユーザーシェルと、シェルスクリプトを動かすシェルは別物です。ユーザーシェルはユーザーが使用すると設定したシェルであり、シェルスクリプトを動かすシェルはシェルスクリプトの一行目のシバン (#!
) で指定したシェルです。最新を含む macOS の /bin/sh
は今でも古い bash (3.2.57) でありシェルは変わっていません。互換性を維持する必要があるので変えていないのでしょう。
新しい bash を使いたいのであれば、Homebrew などで最新の bash をインストールすれば、それをユーザーシェルとしてでもシェルスクリプトを動かすシェルとしてでも使うことが出来ますし、bash をインストールしたとしても zsh やその他のシェルを使うことも出来ます。もちろんターミナルで入力するシェルとシェルスクリプトのシェルが異なれば二つの文法を覚える必要がありますが、シェルスクリプトで使うシェルとターミナルで使うシェルを同じにしなければいけないというわけではありません。(例外として bashrc や zshrc と言った現在のユーザーシェルに読み込むシェルスクリプトは同じシェルにする必要があります。)
ターミナルでインタラクティブシェルとして使うだけであれば、シェルスクリプト用の高度な文法はあまり必要としないと思います。使ってない私が言うのもなんですが、Linux / Unix はシェルを変えられるように設計されているわけで、いろんなシェルを使ってみると良いと思います。
POSIX の仕様改訂には誰でも参加できる
多くの新しいシェルが誕生し、ユーザーは自由にシェルを選ぶことが出来ますが、それでも POSIX シェルの立場を変えることは難しいでしょう。なぜなら OS の一部として指定されてしまっているからです。POSIX で OS の一部として指定されてしまった部分は変化しづらくなります。1990 年代当時の状況を踏まえると POSIX の方針は十分理解できるものですが、いつまでもシェルを不便なままにしておくことはできません。
POSIX は「移植性がある」OS のインターフェースのみを標準化しています。そのため新しいシェルが登場したとしても、それがあらゆる OS に導入されない限り、移植性はないので標準化されることはありません。どの OS も互換性を重視しているのでユーザーシェルならいざしらず、システムシェルを置き換えるようなことはなかなかしないでしょう。OS が POSIX に準拠しようとしている限り、POSIX シェルが他の大きく改善されたシェルに置き換わることはないのです。
シェルスクリプトを使うのをやめて、他の一般的なプログラミング言語を使えば良いのでは?と思うかもしれませんが、標準出力に出力されたテキストを扱い、コマンドを組み合わせるための言語であるシェルスクリプトを他の一般的な言語に置き換えることは出来ません。Perl や Python は awk の代替であってシェルスクリプトの代替ではないのです。事実としてシェルスクリプトはずっと使われ続けていますよね? それにシェルスクリプトを他の言語に置き換えると冗長になるはずです。それは他の言語が適した言語ではない何よりの証拠です。シェルには CLI ユーザーインターフェースとしての役目もありますし、それが不足している他の一般的な言語に置き換えることは不可能です。
システムシェルを変えられない、他の言語に置き換えることも出来ない、となれば、POSIX シェルを少しずつ改善していくしかありません。今は拡張機能であっても、それが多くの POSIX シェルで実装されれば移植性があるものとして標準化されます。ありがたいことに POSIX の仕様改訂には誰でも無料で参加できます。Austin Group のメンバーに登録するだけです。誰でも仕様改訂の提案ができますが、当然それは POSIX の意向に沿ったものでなければなりません。基本的にどの実装にも存在していない新機能が受け入れられることはまずありません。どんなに素晴らしい機能であったとしても POSIX の採択の基準は「すでに移植性があるかどうか」です。もし新機能を標準化させたいのであれば、まず先に既存のシェルやコマンドなど、すでにある実装に機能追加するところから始めなければなりません。最低でも一つ、おそらく一つでは難しいでしょう、二つ以上の実装で移植性が実現されている必要があります。実装が多ければ多いほど良いですが、シェルであれば bash と ksh、コマンドであれば GNU と BSD で、すでに移植性があるものでなければ標準化するのは難しいと思います。完全に同じ動作をしている必要はありません。動作が異なる部分は「POSIX では動作を指定しない」として実装者に任せれば良いからです。重要なのは POSIX として標準化したと言える文章が作れるかどうかです。提案の際には改訂案の文書を作成することが推奨されています。それを元に議論が始まります。
しばらく POSIX のメーリングリストや Issue Tracker を眺めて、どのような方針なのかはなんとなくわかったような気がするので、今後は私も POSIX (主にシェルスクリプト関連)の仕様改訂に参加できればなーと考えています。英語で規格書を書くのは大変ですが、すでにある実装しか標準化されないということは、すでにどこかの実装のドキュメントがあるわけでそれを真似して手直しすればなんとかなるでしょう。おそらく bash 3 ぐらいの機能であれば macOS でもすでに動くわけで十分標準化は可能だと思っています。可能ならば配列も使えるようになって欲しいですね。単語分割に頼らずに複数の単語を一つの変数に入れるには配列は必須です。配列の話は POSIX でも少し前に話題になりましたが否定的な意見はなさそうな気はしています(参考)。他の言語と同じように POSIX シェルも改訂されて古いバッドノウハウ的な物がなくなっていくと良いですね。
さいごに
シェルはあまり変化がないと思われがちですが、そんな事はありません。改訂に時間がかかるのは標準化されたものの宿命だと思いますが、POSIX は拡張機能を妨げないように標準化されているため、bash や zsh は機能追加を続けることができています。
本編中にも書きましたが、シェルおよびシェルスクリプトが他の一般的な言語に置き換わることはありません。多くの優れたスクリプト言語が誕生した今、昔のようにシェルスクリプトから awk を呼び出して事務処理をする必要はないと思いますが、シェルスクリプトは多くのコマンドを組み合わせるのには今も適している言語です。
シェルスクリプトにとって(POSIX コマンドに限らず)多くのコマンドは、ライブラリであり便利な道具です。プログラマであればその道具を使いこなせるようになるべきでしょう。CLI インターフェースとしてシェルから手作業でコマンドを実行するのも良いですが、シェルスクリプトを書いてコマンドをつなげて一連の作業を手早く自動化することも重要です。シェルスクリプトを使わないなどという、なんの意味もなく自分が不幸になるだけの目標なんか立ててないで、シェルスクリプトを書きやすくする方が生産的というものです。そのためにはシェル自体を改善し、シェルスクリプト用ライブラリの開発し、制限が多く移植性も低い古い UNIX コマンド(POSIX コマンド)に依存しないで済むように、代替コマンドの開発が重要だと私は考えています。
本編では時期尚早と考え紹介しませんでしたが、bash から新しい言語へと移行させることが目標の Oil プロジェクト、ライセンス上の理由から BusyBox の代替を目指している toybox とそのシェルである toysh (bash 相当の機能の実装が目標)、名前にシェルとついているわりに CLI が未実装な NGS (Next Generation Shell) など興味深いシェルもあります。Oil (正確には osh)、Busybox ash、toysh は明らかに bash との互換性を目指していますし、いずれは POSIX シェルも bash 3 ぐらいには強化されるのではないかと思います。
シェルの未来は、なにが POSIX で標準化されてるとかされてないとか、それはシェルやコマンドの方言だから使ってはいけないとか、そういうくだらない話から脱却し、シェルやコマンドの互換性を意識せずとも普通に書けばすべての環境で普通に動くようになることを願っています。