読者です 読者をやめる 読者になる 読者になる

hkoba blog

プログラマーです。プログラミング言語ミーハーです。よろしくどうぞ(能代口調)

なぜ私は敢えて Zsh で Shell Script も書くのか、目的合理性はどこにあるのか

zsh

はじめに

UNIX, Linux のためのシェルスクリプトbash どころか敢えて zsh で書くことに、どんな目的合理性があるのか… 個人的な考えをまとめてみます。 #!/bin/zsh の勧めにしたかったけど、途中で力尽きました。

勿論、 万人向けではない話 なので、なるべく背景・仕事環境・与えられた状況についても言及していくつもりです。

ツッコミも歓迎です。

前提

  1. 人生は短い。 限られた時間で、目的を達成したい。 『成果出力 / 学習含めた開発時間』の比を、納得行くレベルに保ちたい。
  2. sh 族の各々には力の差が有る。 プログラミング言語としての表現力の優劣や、処理系の完成度の優劣が存在する。
  3. チームの root と教育を任されている。 自分が個人としてベストな仕事をするだけでなく、メンバーの学習まで含めて判断したい。
  4. 道具は使い分ける。 sh 族で書くメリットのある、向いたタスクだけを sh族で書き、複雑な部分は他の言語で解く。

1. Pure な sh 縛りで書くことのコストに耐えうるか

記憶では…20年以上前、私が研究室にいた頃に、SONY NEWS と Sun だったか…でどちらでも動く /bin/sh スクリプトを書こうとして、 test コマンドの、あるオプションの有無 で泣いたことが、有ったように思います。今ならベンダー間の互換性テストが十分進化して、 こんな事態は起こらないのかもしれませんが…私が Pure な sh 縛りの美辞麗句に疑問を抱くには十分すぎる出来事でした。 条件検査のような、動作の要すら外部コマンドに頼ると、ベンダー間の移植性を確保するために必要な努力は、生半可でなく膨らむのだと気付いたのです。

当時の zsh は既に ksh 流の組み込みテスト [[ を実装していました。(手元の 1993 頃の zsh2.3.1 のマニュアルで確認)。 これを使えば、移植性の問題は zsh 自体の移植の問題に帰着できます。Port zsh or die! (←いえ、移植に貢献できたことは無いですが…)

なお bashrelease note を見ると、 組み込みテスト [[ が入るのは bash-2.02 っぽいですね。 ftp サイトのタイムスタンプ を見ると、 1998-04-18 リリース?なのでしょうか。

(linux の各 distros vendor がいつ↑これを採用したのか、いつか調べてみたいものです)

2. bash/ksh/zsh… 各々の強みを殺して使うコストを払う余裕が有るか

bash/ksh/zsh は互いに切磋琢磨し、機能を向上させてきました。 複数の sh で同じ書き方で使える機能も有れば、そうでないものもあります。 従って、もし複数の sh で互換なコードを書くならば、絶対に使えない機能が出てきます。

一つの具体例を挙げます。

zshbash/ksh との違いの中で、私が最も zsh らしさを感じる点の一つが、 sh_wordsplit のオプション化です。

シェルスクリプトで初心者泣かせは沢山ありますが、最初に注意されることの一つは、 $変数"double-quote" で 囲むことでしょう。変数にスペースやタブが入ったら誤動作するからです。

$ foo="Program Files"
$ ls -dl $foo
ls: cannot access Program: No such file or directory
ls: cannot access Files: No such file or directory
$ ls -ld "$foo"
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 11:53 Program Files

こんな挙動になっている理由は、Pure な sh に配列変数が無かったことが原因でしょう。 配列がなかったから、複数の値を(一つの変数で)直接渡すことが出来ない。 だから代わりに、一つの文字列変数に空白などの区切り文字で繋げて渡し、受け手でそれを分解して使う… それしか実現手段が無かったからです。 この挙動は sh との互換性を優先する限り不可避です。 bash/ksh は今もそれに従っているはずです。

$ mkdir a b c d
$ foo="a b c d"
$ ls -ld $foo
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 a
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 b
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 c
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 d

対して zsh は、私が初めて触れた頃から既にこの挙動を オプション化して オフに していました。

% foo="Program Files"
% ls -ld $foo
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:30 Program Files

もし本当に複数要素を変数に入れるつもりなら、最初から配列変数にしておけば良いという考えでしょう。 (ここに限らず zsh の設計の節々には、 Pure な sh との互換性は程々にして利便性を優先する 、という、勇気ある選択が見て取れます。私はその勇気を賞賛したい)。

昔から、 zsh では代入の右辺を foo=() のように囲むことで、配列変数を作ることが出来ます。

% foo=(a b c d)
% ls -ld $foo
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 a
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 b
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 c
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 d

ちなみに配列の要素に空白が入っても大丈夫です。(唯一の残念な例外は、空文字列そのもの。私が知る限り…)

% foo=(
a b c d
""   ""
"Program Files"
)
% ls -ld $foo
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:30 Program Files
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 a
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 b
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 c
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 d

重要なことは…このような 特定の sh 処理系の進化のメリットを受けたコードを書くためには、 他の処理系との互換性は捨てざるを得ないケースが有る 、ということです。

いつまで "$foo" って書けって叱り続けるの?

勿論、自分だけがシェルスクリプトを書くなら、 "$foo" と書けば良い、で済みます。 ですが、新人教育も自分の仕事だったら?

もっと言えば…教える相手が、自分と同じ方向での (root を期待されるような) unix プログラマーではなく、 他にもっと大事な仕事を抱えていて、片手間の自動化の道具を求めている人だったら?

明らかに、"$foo" の話は苦肉の策、回避策(work around) です。 そして解決策(配列変数を使え)も存在します。 それに新人さんを巻き込み続けることに、どの程度の合理性があるのか…

それよりも、自分が独裁者になって zsh と no_sh_wordsplit を押し付ける、 俺を信じて zsh で書け と言い切るほうが、お互いハッピーになれるのではないでしょうか…

Zsh で書くメリット

ここに記すには余白が狭すぎる(力尽きた)。

代わりに参考文献

英語が読めるならこちら

From Bash to Z Shell: Conquering the Command Line

日本語の本ならこちら

昔 fj に zsh のマニュアルの和訳を投稿していた方の本のはず。 変数展開フラグの解説など充実しているので、 zshスクリプトを書く人にも極めて有益な本です>

www.amazon.co.jp

Zsh で書くデメリット

  • 明示的にインストールする必要がある
  • 教育を請け負う責任が発生する
  • ユーザ向けの UnitTest の枠組みが不足(良いの有ったら知りたいです)

後は…?

.oO(孤独を味わう…とかはあるかもしれませんね…)