POSIX準拠のシェルスクリプトを書くための2つの凄いライブラリ

## はじめに

私がシェルスクリプトを評価している点は移植性が高いスクリプト言語であるという点です。ビルドの必要がなくどこでもファイルをコピーするだけで簡単に動かすことができます。しかし最大の移植性を実現するには bash だけの対応では足りません。POSIX シェルで書く必要があります。機能が少ないためとても大変な作業ですし環境が異なると同じコマンドでもオプションが違ったりと互換性がない場合があります。私の[目標](https://qiita.com/ko1nksm/items/a7b4380f6193b25b3ff9)の一つはその大変な作業を減らし誰でも簡単に POSIX 準拠で移植性のあるシェルスクリプトを書けるようにすることです。それを実現するためには各環境の互換性の違いを吸収し、より安全より便利に簡潔にコードを書くことができるシェル関数ライブラリの開発が必須だと考えています。（つまり JavaScript 版 jQuery です。）できる限り早くシェル関数ライブラリの開発に取り掛かりたいつもりではあるのですが、他にもやることがあるため本格的に始めるにはまだ少し時間がかかりそうです。

同じようなことを考える人は世界中にいるはずです。もしかしたら自分で作らずともすでにそのようなライブラリがあるかもしれません。開発すると決める前に探したのですが、残念ながら私が作りたいものに完全に一致するライブラリはありませんでした。ただし近いライブラリを見つけることはできました。私にとっては競合プロジェクトになるので紹介したくない気持ちもあるのですが、シェルスクリプトの世界をより良くするためです。我慢して紹介したいと思います。

**ただし、私は軽く調べた程度で実際に使ってはいません。紹介だけです。**

## modernish

**Modernish is a library for writing robust, portable, readable, and powerful programs for POSIX-based shells and utilities.**

https://github.com/modernish/modernish

Martijn Dekker さんが開発しているプロジェクトで、安全な変数展開やコマンド展開、ループのための新しい文法、より信頼性がある停止方法、拡張されたトラップ機能、文字列処理、スタック、モジュール機構などを提供することでより自然に信頼性が高いシェルスクリプトを書けるようにするライブラリです。シェル言語を現代的なプログラミング言語にしますが、根本的に変えるようなこと（オブジェクト指向や関数型プログラミングの導入）はしません。[サポートしているシェル](https://github.com/modernish/modernish#appendix-d-supported-shells)は dash, bash, mksh, ksh, yash, zsh などでメジャーな所は抑えられています。（そういや昔は README.md に Modernish という名前は JavaScript の Modernizr が由来だとか書いてあったんですが消えてますね。）

modernish コードの[サンプル](https://github.com/modernish/modernish/blob/master/EXAMPLES.md)です。このコードが POSIX シェル上で動きます。

```sh
#! /usr/bin/env modernish
#! use safe
#! use sys/cmd/harden
#! use var/loop
harden git
harden -e '>1' -f wd_is_clean \
    git diff-index --quiet HEAD
harden -pt touch

git status >/dev/null
wd_is_clean || exit 1 'Working dir not clean'



total=0
LOOP find repofile in . -name .git -prune \
-or -iterate; DO
    # Ask Git for latest commit's timestamp,
    # formatted for POSIX 'touch -t'.
    timestamp=$(git log --format=%cd \
      --date=format:%Y%m%d%H%M.%S \
      -1 HEAD -- $repofile)
    str empty $timestamp && continue

    # 'touch' is traced by 'harden -t'.
    touch -t $timestamp $repofile
    let "total+=1"
DONE
exit 0 "$total timestamps restored."
```

このコードで面白いのが `LOOP` というキーワードで `alias` をうまく使うことでシェルの文法を拡張しています。（実は ShellSpec の開発の初期も同様のテクニックを使ったプロトタイプを開発していました。）

[use sys/cmd/procsubst](https://github.com/modernish/modernish#use-syscmdprocsubst) というモジュールを使うと、例えば ksh/bash/zsh 特有の機能である `diff -u <(ls) <(ls -a)` という書き方も `diff -u $(% ls) $(% ls -a)` と書いて POSIX シェルで動作するようです。どうやって実現しているのかわかりません！とても興味深いテクニックを駆使してシェルを拡張しているのがこのプロジェクトの特徴です。

個人的に見てほしいのが[設計](https://github.com/modernish/modernish/blob/master/share/doc/modernish/DESIGN.md)のドキュメントのこの部分です。

> Optimisation
> 
> Optimise for speed, even if this causes repetitive code. Avoid launching subshells like the plague unless there is no > alternative (command substitution, piping into loops, ( ), all launch subshells).

先日公開した記事（[1](https://qiita.com/ko1nksm/items/fbefb4da7e54e8a29a25), [2](https://qiita.com/ko1nksm/items/936f15702eb962a83a95)）では、パフォーマンス向上のためにコマンド置換やパイプといったサブシェルが作られるコードをなるべく避けると書いたため一部の人が批判していましたが modernish でも同様の結論に達しています。（正直このプロジェクトのことを忘れていたのですが）私のシェルスクリプトプログラミングのテクニックは完全な異端なものではありませんでした。他にもこのドキュメントの内容は私が共感できる内容でいっぱいです。

> The shell is an actual programming language. That's how it was designed and advertised right from the early days of the Bourne shell. Unfortunately, few take it seriously as such.

（シェルは実際にプログラミング言語です。Bourne シェルの最初からそのように設計され宣伝されてきました。残念なことにそれを本気にする人はほとんどいません。）

開発者の Martijn Dekker さんは、現在 ksh 93u+ の 後継プロジェクトの [ksh 93u+m](https://github.com/ksh93/ksh) のメインの開発者で既存の大量のパッチのマージだけではなくそれ以外のバグも多く修正されています。ポリシーの「No major rewrites. No refactoring code that is not fully understood. （大きな書き直しなし、完全に理解してないコードのリファクタリングなし）」と「To help increase everyone's understanding of this code base, fixes and significant changes should be fully documented in commit messages.（他の人の理解を助けるために、修正と重大な変更をコミットメッセージに完全に残す」という厳密な所がとても素晴らしいと思います。コミットメッセージを見てみてください。その説明の量に驚きます。

つまり私が言いたい事は、コマンド置換やパイプを避けるという方針は、POSIX シェルの実装に詳しい技術者の考えでもあるということです。

## shellfire

**A repository of namespaced, composable shell (bash, sh and dash) function libraries. Takes aware the pain of shell scripting, making it robust and reusable. Includes secure curl usage, JSON, XML and Debian control file parsers, dependency documentation via attributes, and more. Batteries ARE included.**

https://github.com/shellfire-dev/shellfire

もう一つの興味深いプロジェクトが shellfire です。modernish のようなシェル文法の拡張は（たぶん）ありませんが、眼を見張るのはそのライブラリの量です。Python 文化でおなじみの Batteries ARE included. (バッテリー同梱）の方針が採用されており、多数のライブラリが付属しています。

個人的に興味が湧いたのが SAX スタイルの JSONリーダーです。XML の初期の頃を知っている方はご存知かと思いますがツリーを生成するのではなくファイルをパースしつつイベント送信（おそらくコールバック関数の呼び出し）すると言う仕組みです。今は DOM スタイルが多く使われていますが DOM スタイルだと配列や連想配列といったシェルスクリプトにはないデータ構造が必要となるため、私はシェルスクリプトには SAX スタイルのほうが適していると考えていて同じような仕組みを考えていました。

他にも `byte`, `compress`, `curl`, `git`, `github`, `jsonwriter`, `random`, `unicode`, `urlencode`, `version`, `xmlwriter` など名前を見るだけでもあれば便利そうだよねというモジュールがたくさんあります。正直この量を見た時、私は自分のプロジェクトを諦めようかと思ってしまったほどです。[サポートされているシェル](https://github.com/shellfire-dev/shellfire#what-platforms-or-shells-are-supported)は dash, bash, ksh88, mksh で残念ながら ksh93, yash, zsh のサポートはしてないようです。

ただこのプロジェクトの問題は 2015 年で開発が停止しているという所です。シェルの互換性は高いのでサポートしているシェルの後継バージョンであれば今でも動くと思いますがぜひ再開して欲しい所です。もちろんオープンソースですしフォークして後を引き継ぐという手はあるんですが ksh93 と zsh に対応してないし（`local` 使ったコードが沢山・・・）パイプの利用は少なめだけどコマンド置換はちょくちょく使われているので、大量のコードを変更するのもなぁといった感じです。