以前のエントリーで、「Ubuntu 10.04 の bash が 4.0 になり、動かないシェル・スクリプトがあって焦った」 と書いた。具体的には制御構造 for のループが意図通りに動かない、と言う問題だ。

Ubuntu は VirtualBox 上でたまに動かす仮想マシンにしかインストールしておらず、この問題もそのまま放置していたのだが、先日久々に Ubuntu 起動させたときに思い出して調べてみると、bash のバージョン云々は勘違いで、実に単純な原因だったことがわかった。

僕が今まで書いたシェル・スクリプトは、1行目の shebang (← 僕は 「シェバン」 派) を "/bin/sh" としていることが多い。これはシェルが ash の VMware ESXi 等、bash が存在しない環境用のスクリプト以外では特に理由はなく、単に歴史的経緯による。Red Hat Enterprise Linux/CentOS や openSUSE、(5.0 からデビューした) Debian 等の日常的に触れる Linux では、/bin/sh が /bin/bash へのシンボリック・リンクになっているため、sh を指定しながら bash の反応を期待することの是非はひとまず置いておくとして、動作自体は全く問題ない。

Red Hat 系だけでなく Debian でも (NAS 製品の LinkStation でも) sh → bash にリンクされているのだから Linux ではこれがスタンダードで、Ubuntu も同じに違いない。となると同じシェル・スクリプトの挙動が Ubuntu で違う理由は、bash のメジャー・バージョンしかない、と思い込んでいたのだ。

ところが問題の Ubuntu では、他の Linux 同様に /bin/sh がシンボリック・リンクであるものの、リンク先が /bin/dash になっていることが、今さらながら判明。dash とは初めて目にした名前だが Debian 版 ash 的な代物で、どうも素の sh を比較的忠実に再現したシェルらしい。と言うことは、個人的にはかなり使用頻度の高い以下のような記述は、/bin/sh が busybox の VMware ESXi 同様に、dash では "array[@]" を "array" としなければ動かない。

array=`find /path/to/hoge/ -maxdepth 1 -type d | sort`
for str in ${array[@]}; do
        echo "${str}"
done

大半のスクリプトは、shebang で "/bin/sh" と言いつつ bash の機能を無意識に多用していて、本当に sh や sh 相当のシェルに出て来られては困ることが多い。名前を呼んでやって来た相手に 「お前なんか呼んでねーよ」 と言い放ってトラブルになった場合にどちらに非があるかを考えれば、ここは一部のスクリプトを除いて shebang を "/bin/bash" に書き換えるのが、最も素直な解決方法だろう。修正対象スクリプトは大量にあるものの、幸い我が家では Linux へのスクリプト配布は Puppet による一元管理体制が整っているため、オリジナル保管庫 (= Puppet サーバーの特定のディレクトリ) 以下で grep して引っかかった全ファイルのパスを配列に放り込んで (bash に依存した) for で回しながら sed すれば、大した手間ではない。一括置換後は、Puppet 経由で配布されるのを待つだけだ (ここで置換に失敗していることに気付かないと、マズいファイルがバラ撒かれて厄介なことになる)。

一方で Ubuntu 側でも、/bin/sh のリンクを bash に変更するオフィシャルな方法が用意されていることがわかった。次のコマンドを実行すると "Install dash as /bin/sh?" と聞かれ、否定すると /bin/sh のリンク先が /bin/bash に変更される。元の dash に戻すには同じコマンドを再度実行して、質問に Yes で答えれば OK。

$ sudo dpkg-reconfigure dash

今後書く (普通の Linux 用) シェル・スクリプトは、"#!/bin/bash" と明示することにしよう。

Comments are closed.