2013-08-07

Shebangという謎な事実上業界標準について

Shebangとは、UNIXのシェルスクリプトの業界標準で、シェルスクリプトの一行目のコメントの、#!を意味する。sheが短母音か長母音か分からなかったので、英語ネイティブにたずねたところ、人によって意見が違う。短母音の方が多数派のようなので、一応シバンが近いものになるだろう。日本語版のWikipediaでも、シバンとしている。この機能には他にも多数の名前があるが、もっとも有名なのが、Shebangだそうだ。

その業界標準的な文法は、以下の通り(ただし、後述するように、この文字列の扱いについては違いがある)

#! 文字列 [改行]

普通、実行権限のついたファイルは、標準のシェルで実行されるが、このShebangがある場合は、#!から改行までの間の文字列を、後述するバラバラな方法で解釈して、execで実行し、その際の引数には元のシェルスクリプトファイルへのパスが指定される。

問題は、このshebangは、規格化されていない業界標準で、誰もが使うにも関わらず、まともにドキュメント化もされていないということだ。その実装には、かなりの差がある。

英語版Wikipediaに詳細な歴史的背景がまとめてある。

Shebangとは、Unix 7と8の間に付け加えられたらしい。同時期にBSDでも実装されている。Unix 8はプロプライエタリとなって一般には使われなくなったので、世間一般には、4.2BSDの実装がよく知られている。

このShebangという名前は、オリジナルの実装者であるデニス・リッチーの命名によるものではない。また、デニス・リッチーの身近にいたペットの名前でもないそうだ。どこかで自然発生したらしいが、初出は明らかになっていない。

問題は、この実装方法、特に#!から改行までの間の文字列の解釈方法に、実装ごとの差異が存在することだ。

あるOSは、#!から改行までの間の文字列を、インタプリターへのパスとみなし、そのまま丸ごとexecに渡して実行しようとする。これは、空白文字も合法なファイルパスであることを考えると、別段おかしくもない。また、shebangをサポートしていないOSもある。

そのようなOSは幸いにも絶滅したが、今ひとつ問題がある。

あるOSは、#!から最初の空白文字までの文字列をインタプリターへのパスとみなし、そのあとに続く空白文字で区切られた複数の文字列を、それぞれインタプリターに対する別々の引数として渡す。

#! インタプリターへのパス 引数1 引数2 引数3 ... [改行]

世の中の実装がみなこうであったなら、話は楽になるのだが、残念ながら別の実装がある。

あるOSは、#!から最初の空白文字までの文字列をインタープリターへのパスとみなし、最初の空白文字から改行までの文字列を、インタプリターへのひとつの引数として渡す。

#! インタプリターへのパス 引数 [改行]

つまり、以下のようなshebangの場合、

#! /usr/bin/interpreter -a -b -c

引数は"-a -b -c"ひとつとなる。これはインタプリターに複数の引数を渡したい場合、問題になる。

厄介なことに、この最後の実装方法は非常によく使われている。たとえば、有名なUNIX風OSの実装である、GNU/Linuxも、この実装を採用している。

多くのGNUのツールは、一文字オプションについて、-abcのようなまとめての指定をサポートしているが、それでは根本的な解決にならない。それに、インタプリター側での対応が必要だ。自分の環境ならば改変も可能だが、他人の環境で実行される場合は難しい。

引数の分割をする独自envもどきを書けば自分の環境では問題解決するが、やはり他人の環境で実行されるシェルスクリプトではそのような解決方法は取りにくい。

また、perlなどのインタプリターとして使われる一部のプログラムは、このようなshebangの実装への対処として、shebangを自分でも解釈して、引数として扱う実装になっている。ただし、そのような対処をしていないインタプリターの場合はどうしようもない。自分の環境ならば改変もできるが、他人の環境で実行されるスクリプトには使えない。

とはいっても、

Shebang (Unix) - Wikipedia, the free encyclopedia
shell - how to use multiple arguments with a shebang (i.e. #!)? - Stack Overflow

ところで、ドキュメント化されていない挙動といえば、GNU bashでは、

$ NAME=VALUE COMMAND

とすれば、あたかも

$ env NAME=VALUE COMMAND

とされたように動作するのだが、この挙動も、GNU bashのドキュメントには見当たらない。これには一体どういう歴史的経緯があるのだろうか。

Bourne-Again SHell manual - GNU Project - Free Software Foundation (FSF)

9 comments:

Anonymous said...

[改行]も問題です。
CRLFだとWindows環境では動くけどLinux環境では動かないなんてことも。

Anonymous said...

最後のはこれでしょうか?

3.7.4 Environment

The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments, as described in Shell Parameters. These assignment statements affect only the environment seen by that command.

江添亮 said...

おお、そんなところに。
気がつかなかった。

sfish said...

1行めですが、
'#' で始まれば、2文字目は '@', '#', '$', '%' 等でも実行できるようです。

こういうスクリプトが、Linux でも FreeBSD で動きました。
---
#/bin/sh
echo "test"
echo $*
---

Anonymous said...

それはおそらくshellによるフォールバック

Anonymous said...

$ NAME=VALUE COMMAND 形式はオリジナルのbourne shellの頃からある「由緒正しき」形式で、Kernighan/Pikeの"The Unix Programming Environment"でも触れられています。多分envコマンドの方が後発じゃないかと思うんですが、envコマンドの歴史の方を見つけられませんでした。

Anonymous said...

Shebangという名前の由来(?)は記号の読み方そのままだと思いますが
#(sharp)!(bang)

江添亮 said...

#(U+0023)はSharpではなくNumber signです。
音楽記号のシャープは、♯(U+266F)です。
もともとhash bangとか呼ばれていたようですね。
sheはsh(シェル)から来ているとする説があります。

Anonymous said...

GNU Smalltalkとかならこれで回避

#!/bin/sh
"exec" "gst" "-I" "SmalltalkDB.image" "-f" "$0" "$0" "$@"
"exit"