純粋なBashでプログラムをどの程度複雑に書くことができますか?[閉まっている]


17

いくつかの非常に迅速な調査の後、Bash はチューリング完全言語であるようです。

なぜBashは、比較的単純なスクリプトを書くためにほぼ排他的に使用されるのでしょうか?BashシェルにはLinuxが付属しているため、他の一般的なコンピューター言語に必要な外部インタープリターまたはコンパイラーなしでシェルスクリプトを実行できます。これは大きな利点であり、場合によっては言語自体の平凡さを補うことができます。

それで、そのようなプログラムがどれだけ複雑になるか制限はありますか?純粋なBashは複雑なプログラムの作成に使用されますか?たとえば、純粋なBashでファイルコンプレッサ/デコンプレッサを作成することはできますか?コンパイラ?シンプルなビデオゲーム?

デバッグツールが非常に限られているという理由だけで、あまり使用されていませんか?


2
shスクリプトconfigure非常に多くのUN * Xパッケージのビルドプロセスの一部として使用される「比較的簡単」ではありません。
user4556274

@ user4556274そうではありませんが、通常は手書きで書かれているのではなく、広範なm4マクロのセットから書かれています。
クサラナナンダ

2
Bashにはx86アセンブラーがあります。そのため、Bashは複雑なプログラムを作成するために時々使用されます。なぜ人々はそれをもっと頻繁にしないのですか?おそらく、インタープリターも遅くてくだらないものであり、「興味深い」バグを起こしやすいためです(fi Shellshockを参照)。また、Bashスクリプトは、サイズの維持が指数関数的に難しくなる傾向があります。上記のアセンブラをご覧ください。ソースからAT&TまたはIntel構文に従っているかどうかを確認できますか?
桂佐藤

configureまた、スクリプトは遅く、役に立たない仕事をたくさん行い、いくつかの面白い暴言の対象になりました。もちろん、シェルは大規模なプログラムに使用できますが、ConwayのGame of LifeおよびMinecraftからコンピューターを作成したこともあり、Brainf ** kやHexagonyなどのプログラミング言語もあります。どうやら、本当に小さくて混乱する原子から何かを構築したい人がいるようです。そのアイデアでゲームを販売することもできます
...-ilkkachu

それで、この質問は答えられるかどうか?彼らはそれを保留にし、答えられないと言うが、それでも私はいくつかの素晴らしい答えを得る。このSEについての質問がどのようなもので、どのようなものが望ましくないのかを示すために、このSEを初めて使用するので、首尾一貫しているのは良いことです。
ブレガラド

回答:


30

Bashはチューリング完全な言語のようです

チューリング完全性の概念は、大規模なプログラミングのための言語で有用な他の多くの概念から完全に分離されています。使いやすさ、表現力、理解力、速度などです。

チューリング完全性が必要なだけであれば、プログラミング言語はまったくなく、アセンブリ言語もありません。CPUもチューリング完全であるため、コンピュータープログラマーはすべてマシンコードを記述するだけです。

Bashが比較的単純なスクリプトを書くためにほぼ排他的に使用されるのはなぜですか?

configureGNU Autoconfによって出力されるスクリプトなど、大きく複雑なシェルスクリプトは、多くの理由で非定型です。

  1. 比較的最近まで、どこにでも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シェル標準であるという事実に対処する必要があります。その標準に基づいて多くの異なるシェルがありますが、それらはすべてその標準からさまざまな程度に分岐しています。再び連想配列を取るために、bashzsh、およびksh93すべてのその機能を持っていますが、複数の実装非互換性があります。あなたの選択は、その後、することであるだけでバッシュを使用、またはのみ Zshのを使用、またはのみ使用しますksh93

    その問題に対するあなたの答えが「Bash 4をインストールするだけ」またはksh93である場合、代わりにPerlまたはPythonまたはRubyを「単に」インストールしないのはなぜですか。多くの場合、これは受け入れられません。デフォルトが重要です。

  2. Bourneファミリのシェルスクリプト言語はいずれもモジュールをサポートしていません。

    シェルスクリプトでモジュールシステムに最も近いのは.コマンドです(別名source、より現代的なBourneシェルバリアント)。これは、適切なモジュールシステムに関連する複数のレベルで失敗します。最も基本的なのは名前空間です。

    プログラミング言語に関係なく、大規模なプログラム全体の1つのファイルが数千行を超えると、人間の理解がフラグを立て始めます。大規模なプログラムを多数のファイルに構成するまさにその理由は、その内容をせいぜい1つまたは2つの文に抽象化できるようにするためです。ファイルAはコマンドラインパーサー、ファイルBはネットワークI / Oポンプ、ファイルCはライブラリZとプログラムの残りの部分との間のシムなどです。多くのファイルを1つのプログラムにアセンブルする唯一の方法がテキストインクルードである場合、プログラムが合理的に成長できる大きさに制限を設けます。

    比較のために、Cプログラミング言語にリンカーがなく、#includeステートメントのみがある場合ようになります。このようなC-lite方言には、externやなどのキーワードは必要ありませんstatic。これらの機能は、モジュール化を可能にするために存在します。

  3. POSIX は、変数を単一のシェルスクリプト関数にスコープする方法定義しません。ファイルに限定します。

    これにより、すべての変数が事実上グローバルになり、モジュール性と構成可能性が再び損なわれます。

    これに対する解決策はbash、POSIX後のシェル(確かに少なくとも)にksh93ありzshますが、上記のポイント1に戻ります。

    GNU Autoconfマクロの作成に関するスタイルガイドでこの効果を確認できます。変数名の前にマクロ自体の名前を付けることをお勧めします。ゼロ。

    このスコアでは、Cでさえ1マイルほど優れています。ほとんどのCプログラムは主に関数ローカル変数で記述されているだけでなく、Cはブロックスコーピングもサポートしているため、1つの関数内の複数のブロックで相互汚染なしに変数名を再利用できます。

  4. シェルプログラミング言語には標準ライブラリがありません。

    シェルスクリプト言語の標準ライブラリはのコンテンツであると主張することは可能ですが、PATH結果を得るにはシェルスクリプトが別のプログラム全体、おそらくより強力な言語で書かれたプログラムを呼び出す必要があると言っているだけですで始まる。

    PerlのCPANのように、シェルユーティリティライブラリの広く使用されているアーカイブもありません。サードパーティのユーティリティコードの大規模な利用可能なライブラリがないと、プログラマは手作業でより多くのコードを記述する必要があるため、生産性が低下します。

    でも、ほとんどのシェルスクリプトが完了有益な何かを得るために通常はCで書かれた外部プログラムに依存しているという事実を無視して、すべてのそれらのオーバーヘッドがありますpipe()fork()exec()呼び出しチェーン。このパターンは、IPCや他のOSで起動するプロセスと比較して、Unixではかなり効率的ですが、ここで、他のスクリプト言語でのサブルーチン呼び出しで行うことを効果的に置き換えています。これにより、シェルスクリプトの実行速度の上限が大幅に制限されます。

  5. シェルスクリプトには、並列実行によってパフォーマンスを向上させる組み込み機能がほとんどありません。

    Bourneシェルにはこのための&waitおよびパイプラインがありますが、それはCPUまたはI / Oの並列処理を達成するためではなく、主に複数のプログラムを作成するためにのみ役立ちます。コアスクリプトをペグしたり、シェルスクリプトだけでRAIDアレイを飽和させることはできません。そうすれば、他の言語ではるかに高いパフォーマンスを達成できる可能性があります。

    特にパイプラインは、並列実行によってパフォーマンスを向上させる弱い方法です。2つのプログラムを並行して実行するだけで、2つのプログラムのうちの1つは、特定の時点で他のプログラムとの間のI / Oでブロックされる可能性があります。

    そこのようなこの周りの末日の方法で、あるxargs -PGNUはparallel、これはただの上に4を指すようにデボルブ。

    マルチプロセッサシステムを最大限に活用する組み込み機能が事実上ないため、シェルスクリプトは、システム内のすべてのプロセッサを使用できる言語で書かれたプログラムよりも常に遅くなります。そのGNU Autoconf configureスクリプトの例を再度取り上げると、システム内のコアの数を2倍にしても、実行速度の改善にはほとんど役立ちません。

  6. シェルスクリプト言語には、ポインター参照がありません。

    これにより、他のプログラミング言語で簡単に行うことができなくなります。

    一つには、プログラムのメモリ内の別のデータ構造を間接的に参照できないということは、組み込みのデータ構造に限定されることを意味します。シェルには連想配列がありますが、どのように実装されていますか?いくつかの可能性、異なるトレードオフとそれぞれがあります。赤、黒の木AVL木、およびハッシュテーブルが最も一般的ですが、他はあります。別のトレードオフのセットが必要な場合は、参照がなければ、多くの種類の高度なデータ構造を手動でロールする方法がないため、行き詰まります。あなたは与えられたものにこだわっています。

    または、依存性グラフをモデル化するために必要になる可能性のある有向非循環グラフなど、シェルスクリプトインタープリターに組み込まれた適切な代替物さえも持たないデータ構造が必要な場合があります。私は何十年もプログラミングを行ってきましたが、シェルスクリプトでそれを行う唯一の方法は、シンボリックリンクを偽の参照として使用してファイルシステムを悪用することです。これは、チューリング完全性のみに依存している場合に得られる一種のソリューションです。ソリューションがエレガント、高速、または理解しやすいかどうかについては何もわかりません。

    高度なデータ構造は、ポインタと参照の用途の1つにすぎません。Bourneファミリーシェルスクリプト言語では簡単に実行できない、他のアプリケーション山ほどあります

続けることができますが、ここでポイントを得ていると思います。簡単に言えば、Unix型システム用のより強力なプログラミング言語がたくさんあります。

これは大きな利点であり、場合によっては言語自体の平凡さを補うことができます。

もちろん、GNU Autoconfがconfigureスクリプト出力にシェルスクリプト言語のBourneファミリの意図的に制限されたサブセットを使用する理由はまさにそのためですconfigure

おそらく、GNUのAutoconf、まだ自分自身の創造の開発者がより高度にポータブルBourneシェルの方言で書くのユーティリティで信者の大きなグループを見つけることができませんPerlで主に書かれている、プラスいくつかのm4、シェルの少しだけ脚本; Autoconfの出力のみが純粋なBourneシェルスクリプトです。それが「どこでもボーン」という概念がどれほど役立つかという疑問を抱かないのであれば、どうなるのか分かりません。

それで、そのようなプログラムがどれだけ複雑になるか制限はありますか?

技術的に言えば、いいえ、チューリング完全性の観察が示唆するとおりです。

しかし、これは、任意のサイズのシェルスクリプトを作成するのが楽しく、デバッグが簡単で、実行が速いということとは異なります。

たとえば、純粋なbashでファイルコンプレッサ/デコンプレッサを作成することはできますか?

「純粋な」バッシュ、何かを呼び出すことなくPATH?コンプレッサーはおそらくecho16進エスケープシーケンスを使用して実行できますが、実行するのはかなり苦痛です。解凍プログラムは、シェルでバイナリデータを処理できないため、そのように書き込むことができない場合があります。最終的にはod、バイナリデータをシェルのネイティブなデータ処理方法であるテキスト形式に変換するために呼び出します。

シェルスクリプトを意図したとおりに使用することについて話し始めると、で他のプログラムを駆動するための接着剤としてPATHドアが開きます。これは、他のプログラミング言語でできることだけに制限されているからです。制限はありません。中に他のプログラムに呼び出すことによって、その力の全てを取得するシェルスクリプトはPATH、高速、より強力な言語で書かれたモノリシックプログラムなどとして実行されませんが、それはない実行します。

そしてそれがポイントです。プログラムを高速で実行する必要がある場合、または他のプログラムから力を借りるのではなく、それ自体で強力である必要がある場合は、シェルで作成しないでください。

シンプルなビデオゲーム?

これがシェルテトリスです。あなたが見に行けば、他のそのようなゲームが利用可能です。

非常に限られたデバッグツールしかありません

大規模なプログラミングをサポートするために必要な機能のリストで、デバッグツールのサポートを約20位に下げました。多くのプログラマーは、言語に関係なく、適切なデバッガーよりもはるかにprintf()デバッグに大きく依存しています。

シェルには、とがechoありset -x、これらを組み合わせることで非常に多くの問題をデバッグできます。


2
「シェルスクリプトには、並列実行を行う組み込み機能がほとんどありません。」私の意見では、シェルは他のほとんどの言語よりも並列処理のサポートが優れています。単一の文字&を使用すると、プロセスを並行して実行できます。wait子プロセスを完了することができます。名前付きパイプを使用して、パイプライン、およびより複雑なパイプのネットワークをセットアップできます。最も重要なことは、ボイラープレートコードをほとんど使用せずに、適切な方法で並列処理を実行し、共有メモリマルチスレッドのリスクと困難を回避することです。
サムワトキンス

@SamWatkins:返信に対処するために、上記のポイント5を更新しました。私も、共有メモリ並列処理に固有の多くの問題を回避する方法として、別々のプロセス間でメッセージをやり取りするのが好きですが、ここで私が指摘したのは、構成可能性などではなく、パフォーマンスの向上です。多くの場合、共有メモリの並列処理が必要です。
ウォーレンヤング

シェルスクリプトはプロトタイピングに適していますが、最終的にプロジェクトは適切なプログラミング言語に移行し、理想的にはコンパイルされた言語に移行する必要があります。次に、極端なケースでは、FFmpegプロジェクトで見られるようにアセンブリします。Cmakeは、Cで書かれており、Perl、Texinfo、またはM4を必要としないAutotoolsに起こるべきことの良い例です。Autotoolsが30年経ってもシェルスクリプトに非常に大きく依存していることを本当に恥ずかしく思いますwikipedia.org/wiki/GNU_Build_System#Criticism
Steven Penny

9

どこでも歩いたり泳いだりできるのに、なぜ自転車、車、電車、ボート、飛行機、その他の乗り物に悩まされるのでしょうか?もちろん、ウォーキングや水泳は疲れる可能性がありますが、追加の機器を必要としないことには大きな利点があります。

1つには、bashはチューリング完全ですが、整数(大きすぎない)、文字列、文字列の(1次元)配列、および文字列から文字列への有限マップ以外のデータの操作には適していません。他の種類のデータには面倒なエンコードが必要です。これにより、プログラムの作成が難しくなり、実際には十分ではないパフォーマンスが課せられることがよくあります。たとえば、bashの浮動小数点演算は難しく、遅いです。

さらに、bashには環境と対話する方法がほとんどありません。プロセスを実行でき、いくつかの単純な種類のファイルアクセス(リダイレクトを介して)を実行できます。Bashには、クライアント側のネットワーククライアントもあります。Bashは十分に簡単にprintf \\0NULLバイトを送信できます()が、入力内のNULLバイトを解析できないため、バイナリデータの読み取りには不向きです。Bashは他のことを直接行うことはできません。そのために外部プログラムを呼び出す必要があります。そしてそれは大丈夫です:シェルは外部プログラムを実行するという第一の目的のために設計されています!シェルは、プログラムを結合するための接着剤言語です。しかし、外部プログラムを実行している場合、そのプログラムが利用可能でなければならないことを意味します-そして、移植性の利点を減らします:)。

Bashには、堅牢なプログラムを簡単に作成できる機能はありませんset -e。(有用な)型、名前空間、モジュール、またはネストされたデータ構造はありません。バグは、プログラミングにおける一番の難しさです。バグのないプログラムを作成することの容易さは、言語を選択する際の決定要因とは限りませんが、bashはその数では不十分です。また、Bashは、プログラムを結合する以外のことを行う場合、パフォーマンスのランクが低くなります。

長い間、bashはWindowsで実行されませんでした。今日でも、デフォルトのWindowsインストールには存在せず、インターフェイスが存在しないという意味で(WSLでも)完全にネイティブに実行されません。 Windowsのネイティブ機能。BashはiOSでは実行されず、Androidにはデフォルトでインストールされません。したがって、Unix専用のアプリケーションを作成しているのでない限り、bashはまったく移植性がありません。

コンパイラを必要とすることは、移植性の問題ではありません。コンパイラは開発者のマシンで実行されます。インタープリターまたはサードパーティのライブラリを必要とすることは問題になる可能性がありますが、Linuxでは配布パッケージを介して解決された問題であり、Windows、Android、およびiOSでは、一般にサードパーティのコンポーネントをアプリケーションパッケージにバンドルします。したがって、念頭に置いている移植性の懸念は、従来のアプリケーションでは実際的な懸念ではありません。

私の答えは、bash以外のシェルにも当てはまります。いくつかの詳細はシェルごとに異なりますが、一般的な考え方は同じです。


1
移植性の神話はかなり頻繁に語られていると思いますが、Javaを含む他のほとんどの言語にも当てはまるので、その特定の項目をネガティブとして使用するかどうかはわかりません。Windowsサーバーと* nixサーバーで実行されているPHPでさえ、Windowsサーバーで何かを実行するのに十分な愚かさがある場合、常に注意しなければならないいくつかの小さな違いがあります。多くのことがAndroidやiOで実行されないため、それがどのように有効なコメントになるのかわからない。
リザード

7

大規模なプログラムにはシェルスクリプトを使用しないいくつかの理由があります。

  • ほとんどの機能は外部コマンドをフォークすることで実行されますが、これは遅いです。対照的に、Perlのようなプログラミング言語は、同等mkdirまたはgrep内部的に行うことができます。
  • Cライブラリにアクセスしたり、直接システムコールを行ったりする簡単な方法はありません。つまり、ビデオゲームを作成するのは難しいでしょう。
  • 適切なプログラミング言語は、複雑なデータ構造をより適切にサポートします。Bashには配列と連想配列がありますが、リンクリストやツリーについては考えたくありません。
  • シェルは、テキストの場合に作成されるコマンドを処理するために作成されます。バイナリデータ(つまり、NULバイト(値がゼロのバイト)を含む変数)を処理するのは困難です。シェルに少し依存し、zshいくつかのサポートがあります。これは、外部プログラムのインターフェイスのほとんどがテキストベースであり\0、セパレータとして使用されるためでもあります。
  • また、外部コマンドのため、コードとデータの分離はわずかに困難です。別のシェルにデータを引用するとき(つまり、実行中bash -c ...またはssh -c ...)にあるすべての問題を目撃します

これは、多くの大きなbashスクリプトを実行する人として、私にとって最も正確なネガのセットです。これらは、おおよそ、ネガとしてもリストするものです。しかし、私が発見したことの1つは、Bashが実際には、同様の機能を比較するとき、他のコンパイルされた言語ほど遅くないことです。私はpythonでbashにあるより複雑なもののいくつかを書き込もうとするとこっそりした疑いがあります。速度の違いは、関係する巨大な作業を価値あるものにしません。ただし、Bashだけでは制限が多すぎますが、Bash + gawkはうまく機能します。gawkはほとんど本物です。
リザード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.