はじめに
シェルスクリプトの世界は今後 10 年で大きく変化します。10 年という数字は切りが良い数字を持ってきただけで根拠はありません。これより長い時間がかかるかもしれませんし、もしかしたら短くなるかもしれません。しかし確実によりよい方向に変わっていくでしょう。Unix/Linux の標準コマンドはさまざまな問題を抱えています。Unix/Linux の標準コマンドに依存している限りシェルスクリプトに大きな改善はありません。これからのシェルスクリプトの世界は Unix/Linux の標準コマンドに依存しない世界です。それがどういうものになるのかをこの記事で解説しています。この記事は私の予言であり目標です。
シェルスクリプトの失われた30年の進化を取り戻す!
残念なことに、シェルスクリプトの世界は 30 年前から大きく変わっていません。それまでの間、プログラミング言語の世界、ソフトウェア開発の世界は大きく変わってしまいました。シェルスクリプトの適用範囲は年々小さくなっています。シェルスクリプトでやれることは限られているため、それは仕方のない話です。GUI アプリケーションやスマホアプリ、音声や画像や動画データの処理、ウェブシステムやクラウドアプリケーション、AI の分野はシェルスクリプトでやるようなことではないので、それら「新しい分野」への対応はできていなくても問題ありません。問題は本来のシェルスクリプトの適用分野です。
シェルスクリプトで何かをやると言っても結局は外部コマンドを呼び出しているだけです。その外部コマンドは他のプログラミング言語で作られています。他の言語を使って「コマンド」を作れば、シェルスクリプトから呼び出すことができます。「新しい分野」への対応は他の言語で行うことです。シェルスクリプトは他の言語で作られたコマンドを利用することで「間接的に」新しい分野に対応することができます。しかし肝心のコマンドを利用する技術が 30 年前からほとんど変わっていません。30 年前に完成した技術であったのなら変わらなくても問題ないのですが、そのようなことはありません。残念ながらシェルスクリプトの世界は不便なまま停滞してしまっているのです。
2024年、POSIX の大幅な改定が行われる
POSIX の大規模な改定が迫っています。前回の POSIX の大規模な改定は POSIX.1-2008 (Issue 7)、間に小規模な修正が行われ厳密には POSIX.1-2017 ですが、それを除くと 15 年も経過してしまいました。そのせいで POSIX は改定されない終息した古い標準規格と勘違いされることもあるようです。次の改定は POSIX 202x (Issue 8) と呼ばれており、まだ正式な規格の名称は定まっていませんが、現在機能が完成された Draft4 まで来ているので、さすがに 2024 年中には完成するでしょう。まあ大幅な改定といってもシェルスクリプトの分野はそこまで大きく変わるわけではありません。重要なのは変化の大きさではなく変化するという事実です。POSIX は変わり続けます。シェルスクリプトの世界も変わり続けます。変わらなければいつまでも不便なままです。何年も同じ知識のままでは通用しません。
POSIX が改定されたからといって、シェルスクリプトの世界がすぐに変化するわけではありません。ただしまだ変化していないというわけでもありません。先進的なシェルや環境を使っていれば今すぐ使えます。原則として POSIX の標準化は後追いで先にいくつかの実装ができてから標準化されます。新しく標準化されるシェルの機能は bash、ksh、zsh で何年も前に実装済みで、set -o pipefail
など BSD 系 Unix では POSIX の動向を見ながら先行して実装されています。realpath
や readlink
など新しく標準化されたコマンドもありますが、それらも GNU や BSD ですでに実装されているものです。POSIX で標準化されていない中でも変化はあります。かつて awk の gensub
は GNU awk の拡張と言われましたが、今や BSD 系 Unix のすべてで対応しています。GNU コマンドとして知られていた seq
コマンドは今や多くの環境で動作します。BSD 系 の sed の仕様は GNU sed にかなり近づいており従来 BSD sed では動かないと言われた書き方が動くようになっています。このように拡張機能として生まれた機能が普及して POSIX で標準化されるという流れです。シェルスクリプトの変化は遅いですが、さすがに 10 年ぐらい前の情報は古くて現状に通用しなくなりつつあります。
さて POSIX の改定やらで少しシェルスクリプトの世界が良くなったからと言ってそれで十分というわけではありません。はっきりいってシェルスクリプトの世界は不便です。他の言語から何年も遅れを取っています。それなら他の言語を使えば良いと思うかもしれませんが、シェルスクリプトは他のスクリプト言語とは全く違う特徴を持っています。簡単に置き換えることができないものなのですが、それが十分に理解されておらずシェルスクリプトの不便さも相まって、シェルスクリプトをなくそうと言われる始末です。まあそういうことを言う人がいても、ずっっっーーーとなくなっていないのが現実なわけですが。シェルスクリプトをなくすのは無理なので諦めましょう。それよりも問題を解決するほうが生産的です。
残念ながら POSIX の改定ではシェルスクリプトの問題を解決することはできません。なぜなら POSIX は Unix / Linux に標準的に含まれている OS のインターフェースといえるコマンドのみを対象としているからです。OS のインターフェースに該当しない便利なコマンドは対象外です。例えばさまざまなプログラミング言語は OS のインターフェースにするようなものではないので POSIX の対象外です。そして互換性を維持する必要があるため、Unix / Linux に標準的に含まれているコマンドは仕様を大きく変更することができないからです。だ・か・ら、Unix / Linux コマンドは仕様がいつまでも変わらずいつまでも不便なままなのです。当たり前ですが不便なものを使えと言っても誰も使うようにならんのですよ。シェルスクリプトの不便さを認めた上で、何が問題であるかを分析しどうすればいいか提案し解決して変化させていくことこそが、シェルスクリプトを愛するものが本当するべきことです。現在のくだらないシェルスクリプト TIPS なんて全部過去のものにしてしまいましょう。
シェルスクリプトの世界の改善は、POSIX に頼ることもできませんが、Unix / Linux ベンダーに頼ることもできません。Unix / Linux ベンダーは OS を開発しており、OS に関係しない部分に関しては OS に含める必要がない(誰かがやればよい)という考えだからです。例えば curl
コマンドや jq
コマンドを OS の標準機能として OS 自身に組み込む必要はないでしょう? OS に組み込んでしまうと OS ごとに互換性がなくなっていくのでデメリットしかありません。そして OS に組み込まれないものは POSIX で標準化されることもありません。つまりシェルスクリプトの世界は OS を開発していない私達が変えていかなければならないということです。
シェルスクリプトの移植性は他の言語に大きな差を付けられた
「UNIXという考え方」という本があります。この中でシェルスクリプトは移植性が高いなどと書かれていますが、それはこの本の原著が書かれた 1990 年頃の話です。当時は Unix で使える言語は、C 言語を除いて Perl ぐらいしか誕生しておらず、1990 年頃の C 言語プログラムの移植性は今よりも大幅に低いものでした。POSIX も標準化されたばかりでまだ準拠している OS がなく、他に比較対象の言語がない時代においてシェルスクリプトは C 言語よりも移植性が高いと言われていただけです。
シェルはどの OS にも標準でインストールされていますが、OS の標準環境で動くことと移植性は別の話です。他のスクリプト言語の多くは移植性が高くどの環境でも動きます。必要なのはスクリプト言語のインタプリタをインストールすることだけです。どちらにしろ OS には何かのソフトウェアをインストールしなければ使い物になりません。何もインストールせずに動くのは手軽ですが、必要なソフトウェアをインストールすればよいだけなので、OS の標準環境で動くことに大きなメリットはありません。シェルスクリプトの目的はコマンドを組み合わせることです。コマンド開発に関しては多くのメリットを持っている新しい言語でやるべきことです。
新しい言語は「UNIXという考え方」が書かれた後の 1990 年代の中頃から登場しました。それらの言語は UNIX の違いを吸収する言語仕様やライブラリを持っており「一度書けばどの環境でも動く」をほぼ実現しました。一方でシェルスクリプトはそのようなことをしなかったため、シェルスクリプトの移植性は30年の間に他の言語に大きな差を付けられてしまいました。30 年も前の古い時代の本を根拠に今もシェルスクリプトを移植性が高いと言うことはできません。他の環境で動くかどうかを気にしながら書かないと高い移植性を実現できないのはシェルスクリプトぐらいです。
30年〜40年前はコンピュータの性能が非常に低く、移植性と効率を両立させることが出来ませんでした。「UNIXという考え方」ではビデオカードのアクセラレータ機能を使うと効率は良くなるが移植性は低くなるというレベルの話をしていて技術も時代も異なります。今のように互換性の違いを吸収する層(ドライバやラッパーライブラリ)を入れたら、大幅にパフォーマンスが下がるから選択肢にならないというような時代なので「UNIXという考え方」は当時の UNIX ベンダーの開発者の考えは分かっても技術的な話は参考になりません。そもそもあの本は技術書ではありません。
シェルとUNIXコマンドは本当に使いづらい
多くの人が感じていることだと思いますが、シェルとUNIXコマンドは非常に使いづらいです。変数の代入でイコールの前後にスペースを入れてはいけない if
の [
]
の前後にはスペースが必要とかいう話ではありません。その問題を解決するのはやぶさかではありませんが、慣れない文法は慣れれば解決するので些細な問題です。
シェルとUNIXコマンドの現状の問題は、重要な機能が不足していながら不必要な機能がたくさんあり、移植性が重要な場合には Linux や macOS などの環境の違いを考慮してシェルスクリプトを書かなければいけないということです。そのような問題は理論的には解決できるはずの問題です。実際に他のプログラミング言語はすでに解決しています。
シェルスクリプトの役目は裏方です。他のプログラミング言語で作るコマンドこそが主役です。他のコマンドを組み合わせるために使うものがシェルや UNIX コマンドですが、組み合わせるためのプログラミングや、それ以前の問題で苦労しているというのが現状です。
シェルスクリプトの世界は不便なまま停滞してしまった
シェルスクリプトの世界は30年前から停滞してしまいました。停滞した理由はいくつかありますが、一番大きな理由は UNIX 開発の大元である AT&T が UNIX の開発から手を引いたことでしょう。UNIX が長い間大きく変化していない理由 「UNIX が完成された OS だから変える必要がない」のではありません。その証拠に UNIX を開発した研究チームは Plan9 という新たな OS を開発していました。Plan9 にもシェルやコマンドはありますが、UNIX のものと互換性があるというわけではなく、より改良されたシェルやコマンドを実装していました。改良というのは機能が多いという意味ではありません。よりシンプルでより簡単に使えるものを目指していました。世が世なら、今のシェルと UNIX コマンドは使われていなかったでしょう。現在のシェルや UNIX コマンドは優れたものではなく、不便なシェルと UNIX コマンドとの互換性を重視して生き残り続けてしまったものなのです。
互換性は重要です。しかしながら互換性のために不便なものが残り続けるというのも良いとは言えません。シェルと UNIX コマンドは互換性を長く保ちすぎました。30 年もあれば物事は確実に変わっていそうなものですが、実質的に UNIX を開発していた AT&T が UNIX の開発をやめ、UNIX を利用していた会社の多くも UNIX の開発に興味がなくなったように思えます。現在 UNIX および UNIX の後継として OS を開発しているのが Linux や macOS ですが、新しい分野の開発が主でシェルや UNIX コマンドなどの昔から「これが UNIX だ」と言われるような分野は停滞してしまっています。世界の興味が新しい分野に移ってしまっているため、シェルスクリプトの世界は「これでいいじゃん」の状態になっているのです。
しかしシェルスクリプトの世界はいつまで経ってもなくなりません。人々は文句を言いながらもシェルスクリプトを書き続けています。多くの人がシェルスクリプトをなくそうとしていますが、どれも実現できていません。それはシェルが他の言語と根本的に違うことを理解できていないためです。シェルを改良するのではなく、シェルを別のものに置き換えようとしているから、シェルスクリプトをなくすことができていないのです。
シェルスクリプトでやるべきこととそうでないこと
シェルスクリプトはどんなことでもやれる道具ではありません。UNIX 哲学の考え方は一つのもので何でもやることではなく適切なものを組み合わせることです。シェルスクリプトでもそれは同じです。シェルスクリプトでも新しく作られたコマンドを使えば、さまざまな事ができます。しかしその「新しいコマンドはどの言語で作ればよいでしょうか?」 そうです。別のプログラミング言語が必要です。古い UNIX の時代では C 言語が唯一のプログラミング言語でしたが、現在はさまざまなプログラミング言語があります。
シェルスクリプトでやるべきことはコマンドを作ることではありません。コマンドも作れますし作っても良いのですが、さまざまな理由からコマンドを作るのは別の言語に任せて、シェルスクリプトではコマンドを組み合わせることに特化するべきです。それがシェルスクリプトでやるべきことです。コマンドの組み合わせ方は色々あります。その一つがパイプですが、パイプはデータとして関連があるものをつなげることしかできません。コマンドを組み合わせる方法はパイプ以外にもあります。さまざまな方法で組み合わせることで、他のプログラミング言語の能力を引き出すことがシェルスクリプトの役目です。
そもそもシェルスクリプト、つまりシェル言語と UNIX コマンドの範囲ではまともなプログラミングはできません。まともなプログラミングとは例えばアルゴリズムの教科書で習うようなアルゴリズムを実装することです。それらのアルゴリズムをシェルスクリプトで実装しようとしたら、メモリを使う代わりにファイルで代用するとか、インデックスの代わりにファイルシステムを使うとか、無理やりな方法で実装しパフォーマンスは大幅に低いという残念な状況になります。シェルスクリプトはプログラミング作業を楽にするための重要な道具ですが、プログラミング教育の中心になるようなものではありません。UNIX コマンドを使うことだけを学んでも、UNIX コマンドを作ることはできません。
ベンダー依存のないこれからの時代に長く使えるコマンドを作る
特定のベンダーが作ったコマンドは、そのベンダーがなくなれば使い物にならなくなります。長く使えるコマンドは多くの人が自由に使えて誰でも自由に開発に参加できるものです。特定のベンダーが仕様を作っているようでは、長く使えるものにはなりません。長く使えるものというのは使用する際に制限がないということです。特定のベンダーが独占した権利を持たずライセンス契約は不要で使用するのにお金もかかりません。どんな個人でもどんな学校でもどんな会社でも制限なく自由に使えるものです。
古くからある UNIX コマンドはこれからも長く使えますが、それだけではこれからの時代に対応できません。これからどころか今の世界にも十分対応が出来ていません。これまでの UNIX コマンドは互換性を保つために残すレガシーコマンドです。
何を作るのか結論は出ています。古い UNIX コマンドの代わりとなる新しいコマンドセットをオープンソースで作ることです。GNU プロジェクトなどが何十年もやっていることと同じことです。POSIX は標準化を行っているだけで、新しいコマンドの仕様を考えたりせず、実際に動くツールを作っているわけでもないので役に立ちません。実際に新しいコマンドを作る必要があります。
独自技術症候群を避け、他人の力を利用するUNIX哲学の考え方
シェルスクリプトの世界で重要な考え方は、他人の力を利用して組み合わせるという考え方です。今まででも Unix コマンドを使って実現できていた? いいえ、足りません。今や多くのコマンドが作られ、しかもオープンソースで誰でも無料で使えるように提供されています。それらをうまく組み合わせられるようにすることが、シェルスクリプトの世界に必要なものです。
「UNIXという考え方」という本にはこのようなことが書いてあります。
よいプログラマはよいコードを書く、偉大なプログラマは良いコードを借りてくる
自分でコードを書くのは時間がかかることです。それよりも他人が作ったコードを利用する方が遥かに簡単で短い時間で目的を達成することが出来ます。これは勉強のために自分で作ることを否定するという意味ではありません。勉強は勉強、それは別の話というだけです。勉強で作ったものは実用目的では使わないようにしましょう。自分で作る必要がないものは他人が作ったものを利用し時間を節約すれば、本当に自分で作らなければいけないことに時間を使うことが出来ます。
シェルスクリプトは、他人の力を利用して時間を節約するための道具です。
「UNIXという考え方」には独自技術症候群を避けるという話も載っています。すべてを自分たちで作っていては、時間はいくらあっても足りません。30 年以上前の古い本なので、特に技術的な内容は現代に役に立たないのですが、考え方は参考になります。
シェルスクリプトの世界は変えなきゃならない!
シェルスクリプトを書いていると馬鹿らしいことがいくつもあります。移植性を考えると更に拍車がかかります。特定のシェル問題やら特定の環境の問題やら令和の時代にやるようなことではありません。これも全部 30 年前から進化が停滞してしまったせいです。
シェルスクリプトの世界は変える必要があります。目指すは「簡単なことをより簡単に」です。
シェル言語や UNIX コマンドの罠やその回避方法や移植性の注意点や POSIX 準拠や豆知識やバッドノウハウをドヤ顔で語るような世界ではだめなのです。そんなものはなくしてしまいましょう。シェルスクリプトがもっと簡単に書けるように慣れば、本当にやらなければいけないプログラミングの世界に集中することができます。
現状の問題点
シェルスクリプトを書く上での問題点を簡潔に表現すると「本質ではない部分に手間がかかっている」です。例えば Linux や macOS で動きが違うとか、対応している正規表現が違うとか、どの環境でも動く書き方をしようとすると使える機能が大きく制限され使いづらいとか、そのような問題の解決に時間がかかるなどです。
前提1: 移植性とはなんぞや?
少し移植性とはなんぞやという話をしたいと思います。例えば次のコードは bash でのみ動くシェルスクリプトです。(補足 厳密にはシバンを変更すれば ksh や zsh でも動きます。シバンの変更は小さい修正なので以下のシェルスクリプトは bash、ksh、zsh の間で移植性が高いと言えます)
#!/bin/bash
for ((i = 1; i <= 100; i++)); do
t=$((t + i))
done
echo "$t" # => 5050
このシェルスクリプトの移植性は高いです。/bin/sh
で動かないから移植性が低い? いいえ、このシェルスクリプトは bash で動くことを明示しているものなので /bin/sh
で動くかどうかは関係ありません。/bin/sh
がどの環境でも使えるシェルというのは csh と比べての話で bash が普及していない 1990 年代までの古い常識です。現在において bash が動かない環境はありません。bash のインストールが必要な環境があるというだけです。bash がどの環境で動くのは POSIX で標準化された C 言語インターフェースを使っており、bash は POSIX に準拠して開発されている POSIX シェルだからです。移植性が高いというのは小さい修正で他の環境で動くことがということです。bash がインストールされていない環境に bash をインストールする作業は小さな修正です。移植性の意味を「OS の最小構成だけで動くこと」と勘違いしないようにしてください。移植性が高い bash を利用すると、どの環境でも動くシェルスクリプトの開発が簡単になるだけではなく bash の拡張機能(進化したシェル言語の機能)を使用して生産性を高めることができます。bash の拡張機能を使わないと /bin/sh
で動くかもしれませんが、生産性は下がり環境ごとに異なるシェルでの動作テストが必要になりシェルスクリプトを開発するコストが増大します。/bin/sh
を使い続ける限り、いつまで経っても進化したシェル言語の機能を使うことができません。/bin/sh
で動くシェルスクリプトは不特定の人への配布用には便利かもしれませんが、自分たちのシステムだけで動かす場合には意味はありません。配布用であっても bash のインストールを使用条件にすれば済む話です。Java ランタイムをインストールすれば Java アプリはどこでも動く話と何も変わらないわけです。
POSIX に準拠しようと思った場合、使用可能な言語やコマンドはいくつもあります。C 言語とPOSIX シェルとPOSIX コマンドだけではありません。POSIX 準拠の条件は「内部的に POSIX で標準化されたインターフェースを使うこと」なので、Rust を使おうが Python を使おうが内部で POSIX インターフェースを呼び出していれば、それは POSIX に準拠したアプリケーションです。というか「POSIX で規定されたコマンド以外を使ったら POSIX 準拠と認めない POSIX 標準規格」だったら誰も POSIX なんて採用しません。それ以外のコマンドということは自分たちが作ったコマンドも含まれますよね。そんなもの使い物になりません。POSIX は「OS のインターフェースを使う場合にはコレを使えば移植性がある」と言っているだけで何をやろうが自由です。OS のインターフェースを使わない場合はなにを使っても POSIX 準拠です。余談ですが、今回の POSIX の改定では C 言語コンパイラは c99
から c17
に変更になります。だからといってこれからは c17
を使わないと POSIX 準拠にならない・・・なんてことはありません。それに POSIX、POSIX、言った所で、Linux も BSD Unix も POSIX に完全に準拠していない(準拠してるのはコレとコレだけ、しかもmacOSなんか20年前の POSIX.1-2001 (Issue 6)のまま)ので、POSIX シェルと POSIX コマンドだけを使ってシェルスクリプトを書いた所で移植性問題が解決するわけがないんです。たとえ POSIX 準拠を主張していても今も実装にはバグがあるので環境ごとの動作の違いは避けられません。POSIX 規格書を読んでわかることは、どの環境でも動くと保証された機能ではなく、動かない可能性が高い機能です。OS が変われば全てテストはやり直しです。
bash に依存したシェルスクリプトを書くことに問題はありません。bash がどこでも動き長く使えるソフトウェアであることは、bash が開発された 1989 年からの 30 年の歴史で証明されています。過去に bash は ShellShock という脆弱性が発見されましたが、それで bash 使えなくなったでしょうか? おそらく脆弱性が発見されたからと言って別のシェルに乗り換えた人はいないでしょう。シェルを変更したらすべてテストし直しですから。現状のシェルスクリプトは OS コマンドへの依存度が高いため、他の言語で作られたアプリケーションのように気軽に OS を変更することはできません。例えば FreeBSD と NetBSD の間にも違いはあるので、普段から継続的にテストをしていない限り、別のシステムでシェルスクリプトが問題なく動作する保証はありません。シェルスクリプトの移植性を高めるには(POSIX コマンドを含む)OS 標準のコマンドへの依存度を減らす必要があります。動作保証をするには十分な時間を掛けてテストする必要がありますが、その時間に比べて bash の脆弱性は短い時間で修正されました。それはオープンソースかつユーザー数が多いからです。長く使えるソフトウェアとはそのようなものです。逆にクローズドソースでユーザー数が少なければ長く使えません。開発元がなくなればそこでジエンドです。GNU bash や GNU コマンドといった特定の OS や特定のベンダーに依存しない長く使えるソフトウェアを使うことが、シェルスクリプトの寿命を長くすることにつながります。
前提2: UNIX/LinuxはUNIXベンダーごとに異なるUNIX/Linuxである
UNIX というのは UNIX ベンダーごとに独立して開発が行われており、原則としてソースコードは共有されていません。元をたどれば AT&T ベル研究所が開発した UNIX にたどり着きますが、そこから始まり、BSD Unix が分岐し、AT&T ベル研究所 の Unix と BSD Unix はそれぞれ異なる UNIX になりました。ある程度お互いの UNIX の成果の取り込みは行われましたが、一つの UNIX を共有しているわけではないので機能や動作に違いがあります。
その後、AT&T の Unix はいくつかに分岐しました。1990 年頃は多くの Unix が存在していましたが、多くは開発が終了し、現在残っていると言えるのは、Solaris、AIX、HP-UX、z/OS、macOS ぐらいでしょう。それらの Unix はそれぞれ異なっており、一つの Unix を共有しているわけではありません。また例外もありますが 商用 Unix は原則としてソースコードが公開されていません。
BSD Unix も同じように分岐しました。AT&T の商用 Unix ビジネスの開始により、BSD Unix から元の AT&T のソースコードは完全に排除されました。そのため System V系 Unix と BSD 系 Unix のソースコードは全く共有されていません。BSD Unix の開発は終了し、その後を引き継いだ BSD 系 Unix には FreeBSD、NetBSD、OpenBSD、DragonFlyBSD があります。それらは途中で分岐したため似ている部分がありますが、それぞれで別々に開発が行われており、分岐してからそれなりの時間が経つため違う部分もそれなりにあります。
Unix とは独立して開発された Linux も、Red Hat Linux や Ubuntu Linux でそれぞれ別の Linux です。すべてが同じ GNU コマンドを採用しているわけではありません。もちろん Unix と Linux の間でソースコードは全く共有されていません。
Unix は POSIX 認証や UNIX の認証として標準化されていますが、標準化の範囲は OS すべてをカバーしているわけではありません。GUI デスクトップと GUI アプリケーションが Unix ごとに全く違うのは言うまでもないでしょう。ベンダーごとに異なる Unix は、その一部だけが標準化されておりその範囲においてのみ移植性があるというのが現状です。POSIX は Unix を作るための標準規格ではありませんが、それは独自の Unix を作って良いということを意味しています。Unix というのは Unix を名乗っていても、それぞれ違う Unix なのです。違う Unix の間でアプリケーションを移植するためのガイドラインが POSIX です。シェルスクリプトの分野である POSIX シェルと POSIX コマンドは、標準化されて共通して使えるといえる部分はわずかしかありません。root 権限が必要になるようなシステム管理系やユーザー管理系のコマンドは最初から POSIX の対象外です。これだけでは移植性があるシェルスクリプトをまともに書くのは不十分です。
シェルスクリプトは他の言語に比べて他の環境に移植するのが難しい
他のプログラミング言語は他の環境に移植するのが簡単です。移植性を気にしているのはもはやシェルスクリプトの世界だけと言っても過言ではないかもしれません。もちろん他の言語でも言語開発者やライブラリ開発者は他の環境への移植性を気にしています。気にしており移植性の問題を解決しているからこそ、一般のアプリケーション開発者は移植性を気にせずにすんでいるのです。
POSIXシェルやPOSIXコマンドの多くは他のUNIXで使えない
シェルやUNIXコマンドの多く、特に POSIX コマンドはどの環境でも使えるので、UNIX ベンダーに依存していないと勘違いしていないでしょうか? それは違います。POSIX の標準規格が特定のベンダーに依存していないことと、それぞれの実装が UNIX ベンダーに依存していないかは意味が違います。一部の例外を除いて、それぞれの UNIX ベンダーが開発した UNIX コマンドは、それぞれの UNIX でしか使えない(他の UNIX で使うことを考慮していない)コマンドです。コマンド名が UNIX ベンダー固有ではないというだけで、各 UNIX ベンダーでそれぞれ実装されています。
各 UNIX ベンダーのシェルやいくつかのコマンドは POSIX に準拠していますが、POSIX がコマンドの仕様を考えて、その仕様に従って各 UNIX ベンダーがコマンドを開発しているわけではありません。順番が逆です。どこかの UNIX ベンダーがコマンドの仕様を考えて実装し、他の UNIX ベンダーが便利だからと同じ仕様で実装します。そしてある程度の移植性があり POSIX で標準化が可能であると判断されたものを標準化しているに過ぎません。それ以外は UNIX ベンダー固有の機能です。だから、UNIXコマンドはさまざまな機能(拡張機能と呼ばれるもの)を持っているが POSIX で標準化されているものしか移植性がないのです。もし POSIX で標準化されているコマンドがあって、その通りに動いていない実装があったらどうなると思います? POSIX の方が仕様を変更して「環境によって動きが異なる (unspecified)」という標準規格になる場合があります。
ほとんどのコマンドの実装は UNIX ベンダーに依存しています。具体的に言うと FreeBSD で実装されたコマンドを、修正なしにそのまま Linux で使うことは原則として出来ません。いくつかの例外もあります。macOS では基本的に FreeBSD のコマンドを導入しているために FreeBSD 版のコマンドとだいたい同じものが使えます。実際には macOS 固有の修正が入っていたりするので、macOS 版のコマンドは macOS 依存のコマンドです。最も大きな例外は GNU コマンドでしょう。これはさまざまな UNIX に移植されているため、GNU コマンドは特定の Linux や UNIX ベンダーに依存していないコマンドです。
UNIX標準のシェルやコマンドを使うから移植性が低くなる
シェルスクリプトは、特定の UNIX ベンダー依存のシェルやコマンド、例えば FreeBSD でしか使えないシェルやコマンドに依存することで移植性が低くなります。これは当然です。FreeBSD でしか使えないシェルやコマンドは Linux で使うことが出来ないからです。
UNIX ベンダー依存ではないシェルやコマンドというのは、例えば bash や GNU コマンドのことです。これらは特定の UNIX ベンダーだけで動くシェルやコマンドではないため、あらゆる UNIX や Linux で同じように動きます。そのような特定のベンダーに依存していないシェルやコマンドを使っているシェルスクリプトは移植性が高くなります。
UNIX標準のシェルやコマンドを使うと拡張機能が使えない
シェルスクリプトの一行目のシバンには /bin/sh
を使用したほうが良い? それは移植性からすれば間違いです。なぜなら /bin/sh
には UNIX ベンダー依存のシェルがインストールされている場合があるからです。例えば FreeBSD では /bin/sh
は FreeBSD sh という FreeBSD 依存のシェルです。
UNIX ベンダー依存のシェルやコマンドを使うと、別の環境でも動くようにするにはどの環境でも動く書き方をしなければいけなくなってしまいます。ここで UNIX ベンダーに依存しないコマンド、例えば bash や GNU コマンドをインストールして使うようにすれば、どの環境でも同じように動き、その上に bash や GNU コマンドの拡張機能を使うことが出来ます。必要なのは bash や GNU コマンドをインストールするという簡単な作業を行うだけです。
しかしながら、bash や GNU コマンドをインストールできない、したくないという状況も多々あるようです。インストール作業なんて UNIX ベンダーが用意したパッケージ管理システムから簡単に行えるし、どちらにしろパッケージをインストールしなければ OS なんて使い物にならないわけで、さっさとインストールすれば良いと思いますけどね。そのために UNIX ベンダーはパッケージ管理システムを用意しているわけで。
POSIX の定義からすれば拡張機能ですが、シェルや UNIX コマンドの拡張機能は、UNIX ベンダーが必要だと考えた重要な機能です。UNIX ベンダーが重要だと考えて追加した機能が、移植性を考えると使えないという残念なことになってしまいます。拡張機能が使えないというのは大問題なのです。
POSIXを意識しないといけない時点で移植性が低いということだ
そもそも移植性の定義ですが、移植性はそのまま動くことではなく、移植のしやすさ、つまり別のシステムで動かすための修正のしやすさです。もしシステム間に完全な互換性があれば、移植する必要はありません。もっとも移植時に修正する部分が少ないものは移植性が高いわけで、その最も高い状態が修正無しで移植できることとも言えるわけで、修正しないで動くことも移植性が高いと言えなくはありません。
移植性が最も高い状態、つまりシステム間で完全な互換性があれば、シェルスクリプトを含むアプリケーションは何も修正せずにそのまま動くことになりますが、知っての通りシェルスクリプトは、何も修正せずに別のシステムで動くことはまずありません。だからこそ POSIX の範囲だけで作らなきゃ! などという話がでてくるわけです。では POSIX の範囲で作っていれば、システム間で完全な互換性がなくても(シェルスクリプトは)動くのか?といえばそうではありません。それは POSIX の役目を誤解しています。
POSIX は完全に互換性がある UNIX を作る標準規格ではありません。POSIX は UNIX の仕様を決めているものでシェルスクリプトを含むアプリケーションを移植するのに必要なインターフェースを定めているだけです。UNIX を作る標準規格でないということの意味は、UNIX はそれぞれのベンダーが自由に作ることができることを意味しています。そして POSIX は 完全な互換性がない UNIX の間で移植、つまり修正の必要性もあると認めた上で、移植しやすくするためのガイドラインです。
POSIX にはアプリケーションを移植するために何に気をつければ良いかが書かれています。POSIX は移植性が高いアプリケーションを作りたい開発者が読むものです。でも他のプログラミング言語では POSIX を読まなくても移植性が高いアプリケーションを作ることが出来ますよね?それは他のプログラミング言語が、完全な互換性がない UNIX システムの違いを吸収する言語仕様やライブラリを持っているからです。OS の機能であるシェルや UNIX コマンドを直接使うシェルスクリプトは、OS の違いを吸収していないから、移植するのが大変なのです。
POSIX シェルの問題点
POSIX シェルは、ほぼ使われなくなった古い Bourne シェルに比べてシェルスクリプトが書きやすくなりましたが、それでもまだ機能不足です。UNIX シェルの後継シェルとなるはずだった Plan9 の rc シェルはもっと多くの機能があります。少なくともそれと同等の機能は使える必要があるでしょう。以下に挙げる POSIX シェルの問題点はほんの一部です。細かい問題はいくつもあります。
配列や連想配列が使えない
シェルスクリプトで配列や連想配列を使わないというのは、あながち間違いではありませんがそれはデータの話です。配列や連想配列はオプションや引数を取り扱うためにあります。例えばコマンドライン引数やシェル関数の引数の位置パラメータ $@
を変数にバックアップするにはどうしたら良いでしょうか? 配列に対応しているシェルではこれだけです。配列に対応していないシェルでは簡単にできません。(補足 引数にはスペースが含まれる場合があることを忘れないように)
args=("$@")
rc シェルでは変数はすべて配列です。UNIX の開発者たちは配列はシェルによって不要なものとは考えていなかったということがわかります。ついでに言うと rc シェルは =
の前後にスペースを入れることが出来ますし、if
の終わりは fi
ではなく { ... }
を使います。
var = value
echo $var(1) # => value
var = (foo bar baz)
echo $var(2) # => bar
連想配列はそれほど使いませんが、オプションを扱うときに使います。
opts["key"]="value"
bash、ksh、zsh いずれも配列と連想配列に対応しています。少なくともこれらのシェルの開発者はシェルにとって必要な機能だと考えたわけですが、POSIX シェルの範囲では規定されていません。
文字列処理が機能不足
POSIX シェルでの変数展開機能の追加で基本的なことができるようになりましたが、それでもまだ機能不足です。一番の問題点は置換機能(${VAR//pattern/str}
)がないことです。この機能がないために簡単な処理に sed
コマンドが必要になってしまいます。
並列実行を行うのが不便
並列実行は今後ますます重要になってきますが、シェルには簡単なバックグラウンド実行の機能しかなく、例えば CPU コアの数だけプロセスを起動するというようなことが簡単にできません。それを実現するために xargs
コマンドや GNU parallel が必要になってきますが、本来はプロセス管理はシェルが行うべきことです。
UNIXコマンド・POSIXコマンドの問題点
UNIX コマンドとは、UNIX に含まれているコマンドのことです。AT&T ベル研究所で UNIX が開発されたときから存在し、UNIX の発展とともに少しずつ追加されていきました。UNIX コマンドが AT&T ベル研究所の外で使われるようになり、それぞれの UNIX でそれぞれ作られていきました。いくつかのコマンドは複数の UNIX で共通に使われている一方で、特定の UNIX ベンダー固有のコマンドも作られていきました。
時が経ち、元の UNIX から含まれていたコマンドは、各 UNIX でそれぞれ修正され同じ名前のコマンドでありながら異なる動きをするようになりました。そのためアプリケーション(シェルスクリプト)を移植するのが次第に困難になっていきました。それが 1980 年代から 1990 年代初めまでの頃の話です。そこで登場したのが POSIX です。POSIX によってコマンドの共通する機能が標準化され、移植性があるシェルスクリプトを書くためにどのようなことに気をつければよいかがドキュメント化されました。しかしそれは UNIX コマンドの問題点を解決するものではありません。POSIX は移植性があるシェルスクリプトを書くためのものであり、使いやすい UNIX コマンドを作っていくものではないからです。
UNIX コマンド・POSIX コマンドにはさまざまな問題点があります。これらの問題点はすべてではなくいくつかの例でしかありません。
UNIXコマンドの仕様は古く合理的でない
UNIX コマンドの仕様は古い時代に作られたものであり、当時のシステム上の制約による制限や、とりあえず作って修正されないまま残っているものがあります。仕様に合理的な理由がないものもいくつもあります。例えば xargs
コマンドです。xargs
コマンドの標準(-0
オプションを指定しない)場合のデータ形式はかなり特殊で、xargs
用のデータ形式で出力するコマンドはほぼ存在しません。
OS の標準コマンドは互換性を保つ必要があるため、OS の一部となったコマンドの仕様は容易に変更できなくなります。POSIX で標準化されるとなおさらです。古い機能は修正せずに残さず新しく機能を追加するしかありません。その結果 UNIX コマンドの仕様は古く合理的でないまま残り続けています。
xargs コマンドの複雑怪奇な仕様を知りたい方は「xargs 完全理解マニュアル - xargs は拡張引数 (extended arguments) の略って知っていますか?」を参照してください。
正規表現が古くコマンド毎に対応に違いがある
UNIX コマンドの正規表現は ed コマンドに最初に実装され、その後各コマンドに複製されていきました。実は正規表現は ed コマンドよりも前に QED というテキストエディタで実装されています。実装したのは Unix を創始者である Ken thompson です。ed コマンドにはコンピュータの性能から完全な正規表現ではなく限定的な正規表現が実装されました。後にコンピュータの性能が向上し完全な正規表現が egrep コマンドに実装されました。
ed または egrep から始まり、さまざまな UNIX コマンドに二種類の系統の正規表現が広まりましたが、コマンドごとで機能が拡張され対応する機能が異なっていました。それをまとめて POSIX で標準化したものが基本正規表現と拡張正規表現です。この歴史からわかるように正規表現は POSIX で標準化されたものが使われているわけではなく、POSIX よりも前にさまざまな UNIX コマンドで実装され、それぞれのコマンドで違いがあるものなのです。POSIX は移植性の観点からそれをまとめましたが、各コマンドの拡張機能は取り入れませんでした。
POSIX で正規表現が標準化された以降も正規表現の進化は止まりません。Perl によって正規表現は格段に進化しました。最新の正規表現は拡張正規表現よりも高度な機能を持っていますが、UNIX コマンドが対応する正規表現は古いままです。さらにコマンドごとに正規表現の対応に違いがあり、その違いにずっと悩まされ続けています。正規表現は進化しましたが、UNIX コマンド、そして POSIX コマンドを使う限り、いつまでも新しい正規表現を使うことは出来ません。
Unicode への対応が中途半端で一貫性がない
POSIX では Unicode を含むマルチバイトのロケールの文字に対応することになっていますが、現実的には対応していないコマンドがあります。商用 Unix はおそらく対応しているはずですが、Linux(GNU コマンド)や BSD 系 Unix では対応していない場合があります。Linux や BSD 系 Unix は POSIX に準拠していると主張してないので、完全対応していなくてもそういうものとして扱うしかありません。
さらに Unicode は数年おきにバージョンアップされます。最初のバージョンは 1991 年の 1.0 で収録されている文字は 7,129 文字です。現在の最新バージョンは 2023 年の 15.1 で収録されている文字は 149,813 文字です。収録されている文字の違いはソート順などに影響します。
それぞれの環境で Unicode への対応は異なり、ロケールに従えば環境ごとで結果が異なる可能性があり、ロケールに従わなければ(C ロケールでバイナリとして扱う)、一文字を一文字として扱うことができません。これを解決すると思われる C.UTF-8 ロケールは環境によって対応していない場合がありますし、話を最初に戻すと環境によってマルチバイトのロケールに対応していないコマンドがあります。
UNIX コマンドはマルチバイトという概念がなかった頃から互換性を保ちながら存在し続けており、その結果 Unicode への対応は中途半端で一貫性がない状態です。
インターネット通信の非対応
Unix はインターネットがない時代に生まれました。POSIX でシェルやコマンドが標準化された 1992 年の時点でもインターネットは普及していない状態です。POSIX は原則として既存のコマンドを標準化するものです。インターネットが普及していない時代の複数の Unix で、インターネット関連のコマンドが広く普及しているはずもなく、したがって POSIX にインターネット関連の機能はありません。また JSON、HTML、XML といったインターネットでよく使われるデータ形式も、後で生まれたものなので当然対応しているコマンドはありません。
単純なテキストデータ形式にしか対応できない
Rob Pike の「Notes on Programming in C」(下記参照)には次のようなルールがあります。
ルール5 重要なのはデータです。もし正しいデータ構造を選び、物事をうまくまとめれば、アルゴリズムはたいてい自明なものになります。プログラムの中心は、アルゴリズムではなく、データ構造です。(詳しくは「人月の神話」を参照してください)
-
Notes on Programming in C
- C言語プログラミングの覚え書き(改訳) 日本語訳 (文章はここからの引用)
ではシェルスクリプトで「正しいデータ構造」を選ぶことは可能でしょうか? ルール4にはデータ構造として次のようなものが書かれています。
ルール4 かっこいいアルゴリズムは単純なアルゴリズムよりもバグが起こりやすく、実装が難しいものです。単純なアルゴリズムと単純なデータ構造を使いましょう。
ほとんどの実用的なプログラムでは、次に挙げるリストのデータ構造があれば十分です。
- 配列
- 連結リスト
- ハッシュテーブル
- 二分木
もちろん、これらを組み合わせて複合データ構造を作る覚悟は必要です。例えば、シンボルテーブルは文字の配列の連結リストを含むハッシュテーブルとして実装されるかもしれません。
歴史的な UNIX コマンドで扱えるのは「単純なテキスト形式」のみです。正しいデータ構造を選ぶというからには、いつだって単純なテキスト形式が正しいデータ構造な訳がありません。既存の UNIX コマンドが「単純なテキスト形式」というデータ構造しか扱えないからという理由で、データ構造を決めるのは、プログラムの中心がアルゴリズム(既存の UNIX コマンドの仕様)になっておりデータ構造が中心にはなっていません。
UNIX コマンドでまとも扱えるのは行を要素とみなした配列に相当するデータ構造ぐらいです。それも要素の中に改行が含まれると破綻します。新しめの形式であるヌル文字区切りを使えば、ヌル文字以外のデータを扱えますが、一行に複数の項目が含まれている場合には破綻します。また UNIX コマンドはテキストファイルを前から後にストリーミングで扱うように設計されておりランダムアクセスに弱いので、結局のところ 「正しいデータ構造」を選ぶ上でテキストファイルには限界があります。
歴史的な UNIX コマンドで扱えるテキスト形式には仕様がありません。わかりやすく言えば CSV や TSV に対応したコマンドが無いということです。オプションを工夫すれば CSV や TSV っぽいデータ形を扱うことができますが、それぞれのコマンドで仕様が一貫しているわけではなく、さまざまな罠があります。データの中にカンマや改行が含まれた CSV データ形式を扱うのが困難なのは言うまでもないでしょう。
最近の awk の CSV 対応(--csv
オプション)によって、この問題は解決へと一歩進みましたが、当然 awk コマンド以外は CSV に対応していません。UNIX コマンドはテキストファイルを扱うことができますが、特定の構造を持った複雑なテキスト形式には対応していません。
新しいシェルスクリプトの世界に起こる変化
シェルスクリプトの世界はこれからも変わり続けます。その変化がどういうものになるかを紹介します。繰り返しますがこれは私の予言なので外れることもありますが、このような変化をおこなさなければシェルスクリプトの世界は便利にはなりません。
POSIX準拠を意識する必要がない世界へ
POSIX 準拠を意識してシェルスクリプトを書くのは非効率で時間の無駄です。なくさなければならないものの一つです。他のプログラミング言語と同じようにライブラリ開発者、シェルスクリプトの分野で言えば、シェル言語ライブラリやコマンド開発者だけが意識すれば十分です。POSIX 標準規格書は一般のシェルスクリプト開発者が読むようなものではありません。
POSIX が標準化の対象とするのは OS インターフェースだけということを忘れないようにしてください。さまざま便利なコマンド、さまざまな優れた言語、それらが POSIX で標準化されない理由は OS のインターフェースでないからです。移植性がないからとか劣っているからではありません。OS のインターフェースでないコマンドは必ず使うので「POSIX コマンドだけを使って頑張るぞ」といってもそれは不可能です。root 権限が必要なコマンドも POSIX 標準化の対象外です。それでどうやって POSIX コマンドだけで頑張ればいいのでしょうか?
POSIX コマンドにないものは C 言語を使って自分でコマンドを作る? その自分で作ったコマンドは POSIX コマンドではありませんよね。C 言語で POSIX API を使って作れば POSIX 準拠だから使ってよい? それなら自分で作らず誰かが作ったコマンドでも POSIX API を使っていれば POSIX 準拠ですよね。世の中にあるさまざまなコマンドや言語は最終的に POSIX API を呼び出しているから POSIX 準拠です。結論としては POSIX で標準化されていないコマンドや言語の多くは POSIX に準拠しているということです。そして POSIX で標準化されていないコマンドや言語は POSIX 準拠を意識することなく使うことが出来ます。なぜなら多くのコマンドや言語は OS の違いを吸収する層を持っているからです。
POSIX は OS のインターフェースとなるコマンドしか標準化しないので、POSIX コマンドは OS の一部といえます。それぞれの OS には違いがあります。つまり OS の一部である POSIX コマンドを直接使うと OS の違いを吸収する層がないことを意味しています。だから POSIX コマンドを使うシェルスクリプトは POSIX 標準規格書を読んで POSIX 準拠を意識しなければ移植性が実現できないのです。反対に OS の違いを吸収した POSIX で標準化されていないコマンドを使ってシェルスクリプトを書けば POSIX 準拠を意識する必要はありません。
「POSIX で標準化されたコマンドや言語はいいものなんだ。なぜなら標準化されているからだ」という理屈に合わない考え方を改め、移植性が高いコマンド、つまり実際に多くの環境に移植されている GNU コマンドや curl
コマンドのようなものを使うことで POSIX 準拠を意識する必要がなくなります。
補足: POSIX が普及した理由の一つは、米政府に納入するコンピューターの要件として POSIX との互換性を要求していたからである。しかしこの要件は 2000年2月25日に廃止されている。(Withdrawn FIPS Listed by Number [PDF] より)
参考 : https://ascii.jp/elem/000/001/786/1786156/
かつては、米政府に納入するコンピューターはPOSIXと互換性が持つ必要(Federal Information Processing Standards:FIPS、FIPS 151-1/151-2がPOSIXとの互換性を要求していた)があり、昔のWindowsはPOSIXサブシステムを内蔵していた。このFIPS 151-1/151-2は2000年に廃止されたため、現行のWindowsはPOSIXとの互換性は持っていない。ちなみにWSLはPOSIXサブシステムをベースにして作られている。
シェルの違いを吸収するシェル言語ライブラリの普及
現状の POSIX シェルにはさまざまな問題があります。一つは機能不足で最小機能しかもたない POSIX シェルでは実現が難しいことがあります。また拡張機能を持ったシェルでもシェルごとに違いがあります。bash などの別のシェルを入れれば改善できますが、過去に作成したシェルスクリプトとの互換性を保つ必要性からシェルを変更できない場合があります。シェル言語ライブラリのメリットは、本体のシェルスクリプトに付け加えて配布できるため、単一ファイルのシェルスクリプトにすることができることです。バイナリの実行ファイルを別にインストールする必要もありません。
シェル言語ライブラリは、それらのシェルの機能不足やシェル間の互換性を解決するためのライブラリです。JavaScript における jQuery のシェルスクリプト版と考えるとわかりやすいと思います。シェル言語ライブラリは原則としてシェル言語の範囲で実装されます。その理由はシェル言語で実装しないとシェル変数やシェル関数などにアクセスすることができないからです。シェル言語の機能を改善するためのものがシェル言語ライブラリです。
シェル言語ライブラリは大きなデータを高速に扱うことは考慮しませんが、小さなデータにおいて十分な速度で動作し、基本的なデータ処理は外部コマンドに依存せずにこれだけで行うことができます。また xargs
コマンドなどに頼らないコマンドの並列実行にも対応可能です。
シェル言語ライブラリは最終的にトランスパイラへと発展する可能性があります。トランスパイラといってもシェル言語を全く別の言語にするわけではありません。JavaScript と互換性がない CoffeeScript が失敗し、互換性が高い TypeScript が普及した歴史に習って、現在のシェル言語を少し改良するトランスパイラです。シェル言語の適用分野を増やすためのものではなく、例えば bash 用のコードを dash で動くようにする程度のシェル言語として便利にするためのトランスパイラです。
汎用の基本コマンドの登場とUNIXコマンドの使用の減少
シェルスクリプトの世界にとって一番必要なものは、UNIX コマンドを置き換える汎用の基本コマンドです。現在の UNIX コマンド、特に POSIX コマンドと呼ばれるものは OS で必要となるコマンドのみを、過去の互換性を保ちながら、古い時代の仕様のまま使われ続けているので非常に不便です。もちろん UNIX コマンドを置き換える新しいコマンドはいくつかありますが、残念ながらコマンド単位で個別に作られており、つまり find
コマンドを置き換える fd
コマンドはありますが、他のコマンドとの統合性に欠けています。
ここでいう汎用の基本コマンドというのは、優れた fd
コマンドを不要にするものではありません。基本のコマンドは基本だけを提供していれば十分です。基本的なことは基本コマンドだけでできる。そのうえで部分的に高度なものが欲しければ専用のコマンドを使うという考え方です。
現在のコマンドは多くの機能を持っています。特に GNU コマンドはたくさんのオプションを持っています。なぜこのようなオプションを持っているのか? それは「Unix 哲学を忘れたからではありません」 互換性を重視したからです。例えば ps
コマンド(これは GNU コマンドではなく procps ですが)は多くのオプションを持っていますが、その理由は System V オプションや BSD オプションを実装してシェルスクリプトの移植性を高めたからです。POSIX は移植性を高めるために「既存のオプションとほぼ同じ機能のオプションを大文字で追加する」ことがあります。例えが xargs
コマンドの -I
オプションは、機能的には昔から -i
オプションとして存在していたものを POSIX で標準化したオプションです。POSIX では -I
オプションですが、それまで使われていた -i
オプションを削除するとそれまでのシェルスクリプトが動かなくなるので、GNU xargs は -i
と -I
の両方のオプションを持っています。
互換性や移植性は重要ですが、どこかで切り捨てなければコマンドは複雑になる一方です。もし Unix コマンドが完璧なコマンドであれば、機能を追加することもなかったでしょうがそうではありません。それぞれの OS で必要と考えた機能を追加し、別の実装では移植性を高めるために同様にオプションを追加し、考え方の違いから少し異なる機能で実装され、それが POSIX で大文字オプションとして追加され、Unix コマンドはもう限界です。互換性を保ちながらシンプルにすることはできません。新しい基本コマンドが必要です。
新しいコマンドはインターネットの対応や音声・画像・動画などの標準化されたバイナリ形式への対応など、OS のインターネットにするようなものではないが、現在必要とされる機能に対応します。最大のパフォーマンスを目指すものではありませんが、現在のトレンドとしてマルチコア CPU への対応はおそらく必要でしょう。マルチスレッドを使用することでコマンド単体でもマルチコア CPU を利用して高いパフォーマンスを出すことができます。C 言語ではなく Rust 製か Go で製になると思いますが、当然 POSIX 準拠となるのでどの環境でも動きます。
最終的には OS 依存が激しい UNIX コマンドを使用する必要性はほとんど無くなるでしょう。勘違いしては欲しくないのは UNIX コマンドはなくならないということです。それは OS の一部でありなくすことは出来ません。UNIX コマンドがなくならないのであれば、UNIX コマンドを使い続ければ良いと思うかもしれませんが、不便な UNIX コマンドをこれから先の 10年、20年も使い続けたいと思いますか? 私は嫌です。UNIX コマンドは移植性は低く使いづらいが OS に標準的にインストールされているコマンドとして選択肢の一つとして生き残ります。
構造化テキスト形式やデータベースへの対応
構造化テキスト形式とは従来の構造がないテキスト形式の問題を解決するものです。構造化テキスト形式の例の一つは CSV 形式です。すべてのコマンドが構造化テキスト形式に対応します。例えば sort
コマンドに変わる新しい並び替えコマンドは CSV 形式(データに改行やカンマが含まれたもの)に対応しておりカラムを指定して正しくソートができます。これは現在の sort
コマンドでは不可能なことです。
データベースへの対応も必要です。構造化テキスト形式に対応したとしても、データベースへの対応は必要です。例えば RDBMS の大きなメリットの一つはデータベースから順番という概念を取り除いたことです。RDBMS ではデータの順番はキーとインデックスによって間接的に定義されており、内部的にソート済みのデータを持っています。これによりユーザーがデータの記憶順を意識せずに、高速な検索とテーブルの結合を実現しています。その他データベースには整合性を維持する様々な機能を持っています。UNIX コマンドにも join
コマンドなどデータベース関連のコマンドがありますが、ユーザーがソート状態を意識して必要な場合に自分でソートを行わなければいけません。データベースからソートの概念を取り除くにはテキストファイルを使っていては実現はほぼ不可能です。高度なデータ管理にはデータベースが必要です。
実際の実装ではおそらく SQLite を使うことになるでしょう。これはデータベースサーバーが不要で、多くの環境で動作し、ファイルの代わりとして使える高速なデータベースです。他の言語との相互運用性も高く、コマンドを組み合わせるためのシェルスクリプトにとってぴったりなプログラムです。
合理的で実用的なUnicode対応と国際化対応
現在のシステムでは Unicode (UTF-8) への対応は不可欠です。他の文字コードや UTF-8 以外への対応は不可能ではありませんが、iconv
などで十分なのでおそらく基本コマンドに含める必要はないでしょう。
合理的で実用的な Unicode 対応とは、データとしての Unicode と人が読み書きする Unicode をきっちりと分けて考えることです。データとしての Uincode は Unicode のバージョンには依存せず文字をコードポイントとして扱います。例えばソートの順番は Unicode のコードポイントで照合順序という概念はありません。その一方で人が読み書きする Unicode には照合順序の考え方が必要です。Unicode のバージョンの違いに対応し、複数の文字で一文字を表す文字などへの対応も必要です。合理的で実用的な Unicode 対応に何が必要であるかはまだ考えがまとまってはいませんが、何かをやる必要はあります。
国際化対応も必要です。例えば数値はどの国でも3桁区切りというわけではありませんし、ある国では小数点記号はピリオドではなくカンマです。3桁のカンマ区切りにだけ対応すれば良いという時代ではありません。国際化対応は必要ですが合理化する必要があります。例えば小数点記号にカンマを用いる場合、小数が含まれる CSV 形式に上手く対応できません。テキスト形式もコンピュータで処理するテキスト形式と人が読み書きするテキスト形とで違いがあります。UNIXコマンドのロケールへの対応は現在はシステム依存となっていますが移植性を考えるとシステムから独立させることになるでしょう。
さいごに - シェルスクリプトはどこでも動き簡単に書けるようになる
シェルスクリプトの移植性で思うことは POSIX があれば、POSIX があれば、全てが解決するんだと勘違いしている人が多いように感じることです。しっかり分析すれば POSIX がどこでも動くことに対して完全な解決策ではないことがわかると思うのですが。POSIX は標準規格であって実装ではありません。UNIX の実装はベンダーごとにバラバラに開発されており同じものはありません。また UNIX コマンド自身も良いものではありません。昔からあるというだけで、おかしな仕様で中途半端なものばかりです。OS の機能に依存している限り OS の違いから逃れることはできません。OS が違うのだから当たり前の話ですよね?
POSIX で標準化されているシェルやコマンドの機能しか使わないと言っている限り、シェル言語やコマンドが進化しても、その進化した機能は使えません。実際に進化したシェルである bash の拡張機能を使えないのでしょう? この記事で紹介した事柄が実現できれば、普通に書いたシェルスクリプトはどの環境でも動くようになります。Linux の場合とか macOS の場合とか気にする必要はありません。そのためにはシェルの違いを吸収するシェル言語ライブラリと汎用の基本コマンドが重要です。bash の拡張機能はライブラリを通してどのシェルでも使えるようになります。これらができればシェルスクリプトはもっと簡単に書けるようになり、生産コストも移植性も大きく向上させることができます。
そんなものを作らずに、根本的に新しいシェルを作ればよいのではないかと考える人がいるかも知れません。それも方法の一つですし、実際新しいシェルを開発している人は何人もいます。しかし私はおそらくそれではシェルスクリプトの世界を変えることは出来ないと考えています。なぜなら世の中には変化したくない人が意外と多くいるからです。変化はゆっくりと行う必要があります。まず今までのシェルスクリプトに追加する形で、シェル言語ライブラリや基本コマンドを組み合わせて使えるようにします。一気に大きく変化させるのではなく、徐々に変化させていくことが重要だと私は考えています。
保守的な考え方が好きな人は、それはそれで今まで通りのやり方を続ければいいと思います。新しいものの方が生産コストは低くなりますが、学ぶのにもコストは掛かりますからね。新しいことを学びたくないというのであれば、それはその人の勝手です。私はシェル言語を進化させるべきだと思っていますし、UNIX コマンドを進化させるべきだと思っていますし、Plan9 を開発していた Unix 開発者たちの考えや POSIX 標準規格が改定するのと同じように変化させることを望みます。