この記事は@piroor さんのコメントに対する私の返信を冗長にポエム化した記事です。私が公開した「シェルスクリプト リファクタリング ~遅いシェルスクリプトが供養されてたので蘇生して256倍に高速化させました~」という記事についての話をしています。
私はシェルスクリプトで"プログラミング"を行っています。私の記事の特に第二章で使っているテクニックはシェルスクリプトとしては異端かもしれませんが、私がリリースしているツール(ShellSpec, getoptions, shdotenv 等)を開発するにあたっての十分な期間と量の検証と実証からの結論であり、そのテクニックが有効であると確信しています。しかし知名度としては「プログラマーの君! 騙されるな! シェルスクリプトはそう書いちゃ駄目だ!! という話」の記事には及ばないため、そのテクニックが理解されず、ましてや間違った変なやり方としてなんの検証もせずに却下されたりするのは私が困ります。
そのため以前から「プログラマーの君! 騙されるな!~」に反論するような形の記事を書こうとしてはいたのですが、自分が意図的にでっち上げたわざとらしい問題が含まれているコードを自分自身で正したとしても説得力が薄く困っていたのですが、今回たまたま多くの人がよくつぶやいている「シェルスクリプトは遅い」ということを示すコードを見つけたので、これをリファクタリングして高速化してやれば(そのテクニックを自分で導入するかどうかは別として)ある程度納得していただけるのではないかと思い記事にしました。
私が「シェルスクリプトは貧弱、使いづらい」という文句を聞くたびに思うのは、じゃあなんで最初から自分の得意な言語で書かなかったの?ということです。シェル自体に詳しくなりたくない、勉強したくないというのなら 1 行のコードであっても最初から Python や Go 等で書けば良いし、既存のコードはさっさと書き直せば良いのです。最初からシェルスクリプトを使ってないのであればシェルスクリプトに対する文句がでるはずがありません。つまりシェルスクリプトに対する文句を言ってる人は、自分が意識してないだけで(その時は)シェルスクリプトが適切だと思ったからシェルスクリプトを使ってるわけです。そこにシェルスクリプトを使うことが適切なユースケースが確かに存在しています。
シェルスクリプトが苦手だという人は自分が得意な言語を使えばいいと思いますが、いちいちシェルスクリプトは遅い、言語が貧弱で分かりづらい、行数が増えるとメンテナンスできなくなる、とかまるでシェルスクリプトに問題があるかのように言う人が多いのが気になります。私にとっては =
や [
前後のスペース問題は、複数の言語を使っている時の行末のセミコロン忘れ程度の些細な問題にしか感じられませんし、遅いというのなら遅くならない書き方や設計をしましょう。記号が多い、文字列処理が分かりづらいというなら分かりやすいライブラリを作りましょう。(strlen ret "$str"
, substr ret "$str" 2:5
みたいな関数でいいですか?作れますよ。)メンテナンス性はコードの書き方で確保するもので正しく設計をすれば大規模な開発もできます。例えば ShellSpec は 1 万行(テストコードは別に 1 万3000 行)を超えるシェルスクリプトとしては大規模なフレームワークですが、小さくモジュール化されており(ファイル数 119 個、関数 971 個)ソフトウェアの品質・メトリクス(ShellMetrics による計測)は 1 関数あたりの論理コード行数は平均 7.48 行、循環的複雑度(11 以上で「少し複雑」とされる)は大部分の関数が 10 未満で平均は 2.37 です。シェルスクリプトで実現可能なことができないとしたら、それはシェルスクリプトの問題ではなくその人の問題です。このようなライブラリを使ってこういうディレクトリ配置でこういう作り方をしてくださいとお膳立てされていなければ自分の力で作れないだけです。自分でライブラリ等を作らなければいけないのでシェルスクリプトは大変というならばそれは事実ですが「シェルスクリプトは便利なライブラリがない」という風な文句はほとんど聞きません。解決可能な問題であることを知らないからだと思います。
私は今後も POSIX シェルがなくなることは無いと思っています。ほとんどの人が使うシェルは POSIX シェルである bash か zsh だろうし、POSIX 関連団体が解散するか脱 POSIX の流れでも生まれない限りシステムシェルが POSIX シェル以外になることはないでしょう。つまりシェル(スクリプト)は誰でも使わなければいけない避けられない言語というわけです。それに対して使いづらいと文句を言いながら使い続けるぐらいならば、使いやすく改善したほうが生産的です。ということでその状況を変えるために、私はシェルスクリプトで"プログラミング"を行って改善するツールやライブラリを作っておりその過程で「プログラマーの君! 騙されるな!~」の記事内容は"プログラミング"には適していないと確信するに至りました。
ちなみに @piroor さんの記事でも登場している Bash Infinity (bash-oo-framework) は私の目的とは異なります。なぜならシェルスクリプト(bash)をシェルスクリプトとは全く異なるオブジェクト指向のプログラミング手法に変えてしまうものだからです。私はフレームワーク(ライブラリ)開発者側とその利用者側を区別して考えており(ある程度混ざることはあるにしろ)そのテクニックは異なるものだと考えています。
例えば Rails などのオブジェクト指向を用いたフレームワークであっても、フレームワーク開発者側はバリバリにオブジェクト指向の技術(継承、多態、委譲 等)を使ってオブジェクト指向的に設計されているのに対して、フレームワークの利用者側が書くコードはどちらかと言えば手続き型に近いコードです。フレームワークの利用者に難しいことはさせる必要はないし、平坦なコードで素早く開発できることが重要なので、私はそれが正しいと思っています。
余談ですが、同じような理由で COBOL (利用者側のコード)を Java (を使ったオブジェクト指向の)コードに置き換えるのは良くないと思っています。だって複雑で大変だったとしても、ただの計算にオブジェクト指向なんて必要ないでしょう?(もっとも COBOL 業界は無知なので思い込みで発言してます。フレームワークと利用者側のコードが明確に分離されていて、通常は COBOL を使ったのと同じように手続き型で書ける状況になっているのであれば問題はありません。)
bash-oo-framework のようにシェルスクリプトの利用者側のコードを大きく変更してしまうようなフレームワークは私の目的ではありません。利用者側のコードのスタイルを変えること無くよりシンプルに簡単に書けるようにすることを目的としています。そしてフレームワークやライブラリの開発では、それを実現するために様々なテクニックを使用します。つまりシェルスクリプトの"プログラミングとしてのテクニック"(第二章のテクニック)は、シェルスクリプト用のフレームワークやライブラリを書くための「私のためのテクニック」です。
シェルスクリプトは汎用で万能のスクリプト言語ではありません。しかしそれは貧弱な言語などではなく、目的(既存のコマンドの組み合わせで最短の手段で物事を実現する)に不要なものをあえて取り入れずにシンプルに保っているだけです。配列とか連想配列とかオブジェクト指向とか、最終手段としてはあってもいいと思いますが基本的にはシェルの目的には不要な機能だと思います。
シェルスクリプトは特定の目的を最短で実現するために特有の考え方が必要だったりしますが、人々が思うよりも柔軟で十分な速度を実現することができる言語です。ただ少し今はライブラリが足りておらず、それを実現するための技術が確立・普及していません。その問題を解決しシェルスクリプトを本来のシェルスクリプトの用途でより簡単に使えるようにすることを目指して私はシェルスクリプトで"プログラミング"を行っています。