Bashはチューリング完全な言語のようです
チューリング完全性の概念は、大規模なプログラミングのための言語で有用な他の多くの概念から完全に分離されています。使いやすさ、表現力、理解力、速度などです。
チューリング完全性が必要なだけであれば、プログラミング言語はまったくなく、アセンブリ言語もありません。CPUもチューリング完全であるため、コンピュータープログラマーはすべてマシンコードを記述するだけです。
Bashが比較的単純なスクリプトを書くためにほぼ排他的に使用されるのはなぜですか?
configure
GNU Autoconfによって出力されるスクリプトなど、大きく複雑なシェルスクリプトは、多くの理由で非定型です。
比較的最近まで、どこにでもPOSIX互換のシェルを用意することはできませんでした。
多くのシステム、特に古いシステムでは、技術的にはシステムのどこかにPOSIX互換のシェルがありますが、のような予測可能な場所にない場合があります/bin/sh
。シェルスクリプトを記述していて、多くの異なるシステムで実行する必要がある場合、シェバン行をどのように記述しますか?1つのオプションは、先に進んでを使用する/bin/sh
ことですが、そのようなシステムで実行される場合に備えて、POSIX以前のBourneシェル方言に限定することを選択します。
POSIX以前のBourneシェルには、算術演算さえ組み込まれていません。あなたはそれを呼び出すか、expr
それbc
を成し遂げなければなりません。
POSIXシェルを使用しても、Perlが1990年代初頭に最初に普及して以来、Unixスクリプト言語で見られる連想配列やその他の機能が欠落しています。
その歴史の事実は、現代のBourneファミリーシェルスクリプトインタープリターの多くの強力な機能を純粋に無視するという数十年にわたる伝統があることを意味します。
バッシュは、連想配列を取得していない:これはまだ実際には、この日に続けて、バージョン4までが、あなたは船のMacOSとのバッシュ3は2017年に、まだ、まだ使用されているがバッシュ3.アップルに基づいていますどのように多くのシステム驚かれるかもしれない- どうやらためにライセンスの理由 — Unix / Linuxサーバーは多くの場合、実稼働環境で非常に長い間ほとんど影響を受けずに実行されるため、CentOS 5ボックスなどのBash 3を実行している安定した古いシステムがあります。環境にこのようなシステムがある場合、それらで実行する必要があるシェルスクリプトで連想配列を使用することはできません。
その問題に対する答えが「現代の」システム用のシェルスクリプトのみを書くことである場合、ほとんどのUnixシェルの最後の共通の参照ポイントはPOSIXシェル標準であるという事実に対処する必要があります。その標準に基づいて多くの異なるシェルがありますが、それらはすべてその標準からさまざまな程度に分岐しています。再び連想配列を取るために、bash
、zsh
、およびksh93
すべてのその機能を持っていますが、複数の実装非互換性があります。あなたの選択は、その後、することであるだけでバッシュを使用、またはのみ Zshのを使用、またはのみ使用しますksh93
。
その問題に対するあなたの答えが「Bash 4をインストールするだけ」またはksh93
である場合、代わりにPerlまたはPythonまたはRubyを「単に」インストールしないのはなぜですか。多くの場合、これは受け入れられません。デフォルトが重要です。
Bourneファミリのシェルスクリプト言語はいずれもモジュールをサポートしていません。
シェルスクリプトでモジュールシステムに最も近いのは.
コマンドです(別名source
、より現代的なBourneシェルバリアント)。これは、適切なモジュールシステムに関連する複数のレベルで失敗します。最も基本的なのは名前空間です。
プログラミング言語に関係なく、大規模なプログラム全体の1つのファイルが数千行を超えると、人間の理解がフラグを立て始めます。大規模なプログラムを多数のファイルに構成するまさにその理由は、その内容をせいぜい1つまたは2つの文に抽象化できるようにするためです。ファイルAはコマンドラインパーサー、ファイルBはネットワークI / Oポンプ、ファイルCはライブラリZとプログラムの残りの部分との間のシムなどです。多くのファイルを1つのプログラムにアセンブルする唯一の方法がテキストインクルードである場合、プログラムが合理的に成長できる大きさに制限を設けます。
比較のために、Cプログラミング言語にリンカーがなく、#include
ステートメントのみがある場合のようになります。このようなC-lite方言には、extern
やなどのキーワードは必要ありませんstatic
。これらの機能は、モジュール化を可能にするために存在します。
POSIX は、変数を単一のシェルスクリプト関数にスコープする方法を定義しません。ファイルに限定します。
これにより、すべての変数が事実上グローバルになり、モジュール性と構成可能性が再び損なわれます。
これに対する解決策はbash
、POSIX後のシェル(確かに少なくとも)にksh93
ありzsh
ますが、上記のポイント1に戻ります。
GNU Autoconfマクロの作成に関するスタイルガイドでこの効果を確認できます。変数名の前にマクロ自体の名前を付けることをお勧めします。ゼロ。
このスコアでは、Cでさえ1マイルほど優れています。ほとんどのCプログラムは主に関数ローカル変数で記述されているだけでなく、Cはブロックスコーピングもサポートしているため、1つの関数内の複数のブロックで相互汚染なしに変数名を再利用できます。
シェルプログラミング言語には標準ライブラリがありません。
シェルスクリプト言語の標準ライブラリはのコンテンツであると主張することは可能ですが、PATH
結果を得るにはシェルスクリプトが別のプログラム全体、おそらくより強力な言語で書かれたプログラムを呼び出す必要があると言っているだけですで始まる。
PerlのCPANのように、シェルユーティリティライブラリの広く使用されているアーカイブもありません。サードパーティのユーティリティコードの大規模な利用可能なライブラリがないと、プログラマは手作業でより多くのコードを記述する必要があるため、生産性が低下します。
でも、ほとんどのシェルスクリプトが完了有益な何かを得るために通常はCで書かれた外部プログラムに依存しているという事実を無視して、すべてのそれらのオーバーヘッドがありますpipe()
→ fork()
→ exec()
呼び出しチェーン。このパターンは、IPCや他のOSで起動するプロセスと比較して、Unixではかなり効率的ですが、ここでは、他のスクリプト言語でのサブルーチン呼び出しで行うことを効果的に置き換えています。これにより、シェルスクリプトの実行速度の上限が大幅に制限されます。
シェルスクリプトには、並列実行によってパフォーマンスを向上させる組み込み機能がほとんどありません。
Bourneシェルにはこのための&
、wait
およびパイプラインがありますが、それはCPUまたはI / Oの並列処理を達成するためではなく、主に複数のプログラムを作成するためにのみ役立ちます。コアスクリプトをペグしたり、シェルスクリプトだけでRAIDアレイを飽和させることはできません。そうすれば、他の言語ではるかに高いパフォーマンスを達成できる可能性があります。
特にパイプラインは、並列実行によってパフォーマンスを向上させる弱い方法です。2つのプログラムを並行して実行するだけで、2つのプログラムのうちの1つは、特定の時点で他のプログラムとの間のI / Oでブロックされる可能性があります。
そこのようなこの周りの末日の方法で、あるxargs -P
とGNUはparallel
、これはただの上に4を指すようにデボルブ。
マルチプロセッサシステムを最大限に活用する組み込み機能が事実上ないため、シェルスクリプトは、システム内のすべてのプロセッサを使用できる言語で書かれたプログラムよりも常に遅くなります。そのGNU Autoconf configure
スクリプトの例を再度取り上げると、システム内のコアの数を2倍にしても、実行速度の改善にはほとんど役立ちません。
シェルスクリプト言語には、ポインターや参照がありません。
これにより、他のプログラミング言語で簡単に行うことができなくなります。
一つには、プログラムのメモリ内の別のデータ構造を間接的に参照できないということは、組み込みのデータ構造に限定されることを意味します。シェルには連想配列がありますが、どのように実装されていますか?いくつかの可能性、異なるトレードオフとそれぞれがあります。赤、黒の木、AVL木、およびハッシュテーブルが最も一般的ですが、他はあります。別のトレードオフのセットが必要な場合は、参照がなければ、多くの種類の高度なデータ構造を手動でロールする方法がないため、行き詰まります。あなたは与えられたものにこだわっています。
または、依存性グラフをモデル化するために必要になる可能性のある有向非循環グラフなど、シェルスクリプトインタープリターに組み込まれた適切な代替物さえも持たないデータ構造が必要な場合があります。私は何十年もプログラミングを行ってきましたが、シェルスクリプトでそれを行う唯一の方法は、シンボリックリンクを偽の参照として使用してファイルシステムを悪用することです。これは、チューリング完全性のみに依存している場合に得られる一種のソリューションです。ソリューションがエレガント、高速、または理解しやすいかどうかについては何もわかりません。
高度なデータ構造は、ポインタと参照の用途の1つにすぎません。Bourneファミリーシェルスクリプト言語では簡単に実行できない、他のアプリケーションが山ほどあります。
続けることができますが、ここでポイントを得ていると思います。簡単に言えば、Unix型システム用のより強力なプログラミング言語がたくさんあります。
これは大きな利点であり、場合によっては言語自体の平凡さを補うことができます。
もちろん、GNU Autoconfがconfigure
スクリプト出力にシェルスクリプト言語のBourneファミリの意図的に制限されたサブセットを使用する理由はまさにそのためですconfigure
。
おそらく、GNUのAutoconf、まだ自分自身の創造の開発者がより高度にポータブルBourneシェルの方言で書くのユーティリティで信者の大きなグループを見つけることができませんPerlで主に書かれている、プラスいくつかのm4
、シェルの少しだけ脚本; Autoconfの出力のみが純粋なBourneシェルスクリプトです。それが「どこでもボーン」という概念がどれほど役立つかという疑問を抱かないのであれば、どうなるのか分かりません。
それで、そのようなプログラムがどれだけ複雑になるか制限はありますか?
技術的に言えば、いいえ、チューリング完全性の観察が示唆するとおりです。
しかし、これは、任意のサイズのシェルスクリプトを作成するのが楽しく、デバッグが簡単で、実行が速いということとは異なります。
たとえば、純粋なbashでファイルコンプレッサ/デコンプレッサを作成することはできますか?
「純粋な」バッシュ、何かを呼び出すことなくPATH
?コンプレッサーはおそらくecho
16進エスケープシーケンスを使用して実行できますが、実行するのはかなり苦痛です。解凍プログラムは、シェルでバイナリデータを処理できないため、そのように書き込むことができない場合があります。最終的にはod
、バイナリデータをシェルのネイティブなデータ処理方法であるテキスト形式に変換するために呼び出します。
シェルスクリプトを意図したとおりに使用することについて話し始めると、で他のプログラムを駆動するための接着剤としてPATH
ドアが開きます。これは、他のプログラミング言語でできることだけに制限されているからです。制限はありません。中に他のプログラムに呼び出すことによって、その力の全てを取得するシェルスクリプトはPATH
、高速、より強力な言語で書かれたモノリシックプログラムなどとして実行されませんが、それはない実行します。
そしてそれがポイントです。プログラムを高速で実行する必要がある場合、または他のプログラムから力を借りるのではなく、それ自体で強力である必要がある場合は、シェルで作成しないでください。
シンプルなビデオゲーム?
これがシェルのテトリスです。あなたが見に行けば、他のそのようなゲームが利用可能です。
非常に限られたデバッグツールしかありません
大規模なプログラミングをサポートするために必要な機能のリストで、デバッグツールのサポートを約20位に下げました。多くのプログラマーは、言語に関係なく、適切なデバッガーよりもはるかにprintf()
デバッグに大きく依存しています。
シェルには、とがecho
ありset -x
、これらを組み合わせることで非常に多くの問題をデバッグできます。
sh
スクリプトconfigure
非常に多くのUN * Xパッケージのビルドプロセスの一部として使用される「比較的簡単」ではありません。