11th
■ #!/usr/bin/env
_ 今さらながら、 CGIの神話と現実というエントリ。ただし内容とはまったく関係なし。スクリプト言語の shebang 行(#!)でスクリプトインタープリタを直接書かずに /usr/bin/env でラップするという悪習はいったい誰が広めたんだろうか。ruby な人に多いように思うがそれだけじゃないし。
_ hoge というインタープリタを使うスクリプトの1行目をたとえば #!/usr/bin/env hoge としておくと、hoge の実行バイナリが /usr/bin/hoge でも /usr/local/bin/hoge でも $HOME/bin/hoge でもどこにあってもパスが通ってさえいれば起動できる。それはメリットなんだろうか。わしにはむしろデメリットにしか思えない。
_ /usr/bin/env を使うというのは、PATH の値にしたがってインタプリタの場所を探すという意味である。つまり、スクリプトを書いた人と実行する人で設定が違えば同じインタープリタが起動するとは限らない。OS 標準の /usr/bin/perl と、そのホストで独自にコンパイルした /usr/local/bin/perl と、ユーザが自分用にコンパイルした $HOME/bin/perl とが存在している場合、#!/usr/bin/env perl となっていてはどの perl が使われるのかわからないし、もしかしたらそもそもインタープリタを見つけられないかもしれない。どこにインストールされているかに依存せずに実行できる、というけれど、$PATH という別のものに依存するようになっただけだ。異なるバージョンのものを複数インストールしてある環境は珍しくないと思うのだが、同じ名前でも同じ動作をするとは限らない(同じ動作をするのならいくつもインストールする意味がない)わけで、インタープリタの位置を明示しない記法というのは害になっても益になることはないと思うのだが。
_ OS によっては env が /usr/bin ではなく /bin に存在するものもあるらしい(MacOSX 10.1 がそうらしい)。env 自身が環境依存という時点で、環境依存性の回避というのは幻想に過ぎないと斬って捨ててもいいと思うんだが、仮に幻想のまま /usr/bin/env を前提にできるとして、それでもなお意図しない動作をしてしまう例を挙げてみる。
_ たとえば FreeBSD では、標準の /etc/crontab は
となっていて /usr/local/bin が含まれていない。なので、PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbinというスクリプトを /etc/crontab に登録すると、FreeBSD では ruby は /usr/local/bin にあるのが標準なので、インタープリタを見つけられずにエラーになる。#!/usr/bin/env ruby_ Apache は起動時に環境変数をクリアしない。なので、OS 起動時に自動で起動される場合と、PATH=$HOME/bin:/usr/local/bin/usr/bin:/bin な環境で sudo apachectl start する場合では PATH の値が異なることがある。CGI にわたされる PATH は Apache 起動時の PATH がそのまま使われるから、つまり、/usr/bin/env でラップしている CGI では、Apache がどのように起動されたかによって異なるインタープリタが使われる(あるいはインタープリタを見つけられない)ことがある。
_ こういう問題は cron や apache にかぎらず、シェルのコマンドラインからではなくデーモン類から起動されるもの全般で発生する。たとえばメールの自動処理をするために aliases や .forward、.qmail、.procmailrc などからスクリプトを起動する場合にも、MDA がスクリプトにわたす PATH の値によって同じような問題が起きるだろう。
_ そもそも、
のように複数の引数を shebang 行に書いた場合、/path/to/cmd にどんな引数が渡されるのかは OS によって異なる。”a b c” というひとつの引数が渡されるもの(Linux とか FreeBSD >=6 とか)、”a” “b” “c” という3つの引数が渡されるもの(FreeBSD <=5 とか)、”a” だけが渡されるもの(Solaris とか)、さまざまである( 参考)。つまり、#!/path/to/cmd a b cならば、引数がひとつだけなのでどんな環境でも書いたとおりに /usr/bin/perl -Tw が実行されるが、 ここにあるように#!/usr/bin/perl -Twとするとそうはいかない。FreeBSD 5.x では意図どおり動くが、Solaris では -Tw が無視され (*1)、Linux では “perl -Tw” なんてコマンドはないというエラーになる。perl がどこのパスにあっても起動できるよう環境非依存にするつもりで /usr/bin/env を使ったのだろうが、逆に /usr/bin/env を使ったが故に環境依存の罠にハマっていることになる。これはまた、#!/usr/bin/awk -f や #!/usr/bin/sed -f のように shebang 行で引数が必須の言語は多くの環境で #!/usr/bin/env awk -f のように代替できないことを意味する。#!/usr/bin/env perl -Tw_ それでも #!/usr/bin/env の方がすぐれているという方、反論求む。
(*1): ただし、OS レベルでは無視されるが、perl 自身がスクリプトに書かれた shebang 行を解釈するので、perl として起動された後で perl -Tw として起動されたかのように動作を変更しようとする(その結果エラーになる)。っていうか、perl hoge.rb とか perl hoge.sh とかでも、shebang 行さえ書いてあれば ruby なり sh なりにスイッチしてちゃんと動いてしまう。ruby も perl と同じように shebang を解釈するが、あらゆるスクリプト言語が同じように shebang 行を解釈してくれると思ったら間違いである。
どさにっき (via sanemat) (via otsune)
ああ、これの反論を前書いたと思って探してみたけどWebには残ってねえなあ。http://mput.dip.jp/mput/?date=20060626#p01 に書いてたはずなんだけどなあ。しょうがないからもっかい書くか。
えっとねえ、結論から言うけど、この記事の人は配布する側の苦労をわかってない。 #! /usr/bin/envは実用上極めて広範囲に動くからデフォルトとして無難なんだよ。だから/usr/bin/envがない環境があるとか、知ってるよ?でもじゃあ代わりにどう書けってのさ。CGIをtarで落としてきて手元で展開してFTPでサーバにアップロードする、シェルアカウント持ってない人のために、一体#! /usr/bin/envの代わりにどう書けってんだよ! /usr/bin/env 動かないひとのための個別対応を結局するとしても、対応しなくて済むユーザーのことを考えたら費用対効果としては十分すぎるんだよ。そりゃおまえらが自分で書いたスクリプトなら好きに正しいパスを書けや。でもCGIってそうじゃないだろ。配布してるんだから。どうやって概ねの環境でそこそこ動くかでCGI作家はそれなりに悩んでノウハウ貯めてるんだよ。envもその一環なの。否定するなら対案を示せといいたい。
(なお引用元も俺の反論も2006年時点での状況を前提としておりますことをご了承ください。当時まだGumblarはありません)