rc (sh) magic

ファイルが"#!"で始まっていると,カーネルはそのファイルを(シェル)スクリプトだと判断して,"#!"に続くプログラムを起動して,そのファイルを実行する.というのが,UNIXスクリプト起動の仕掛けだが,Plan9でもそこは同じである.
さて,シェルスクリプトについて調べていたとき,POSIX的には"#!/bin/sh"じゃなくて,"#!"と"/bin/sh"の間に空白があるのが正しいという記述を見かけた.(POSIX仕様は調べてないけど)確かに,magicファイルには次のように書かれている.

0       string/b        #!\ /bin/sh             Bourne shell script text executable

それでもfileコマンドは両方のケースをちゃんと"Bourne shell script text executable"と認識する.ソースまで読んでないけど,magicファイルに頼らず,fileコマンドの中でチェックしているんだろうな.

$ file hoge.sh
hoge.sh: Bourne shell script text executable

一方,Plan9にはmagicファイルは存在しないようだ.fileコマンドは"#!/bin/rc"に完全マッチングするかどうかで判定している.なので,"#!"と"/bin/rc"の間に空白があると,fileコマンドはrcスクリプトと判定しない.

cpu% cat hello.rc
#!/bin/rc
echo "Hello, world!"
cpu% file hello.rc
test.rc: rc executable file
cpu% hello.rc
Hello, world!

cpu% cat hello.rc
#! /bin/rc
echo "Hello, world"
cpu% file hello.rc
test.rc: English text
cpu% hello.rc
Hello, world!

もちろん両方のスクリプトとも実行可能である.では,核心であるexec(2)を見てみよう.結局,先頭の2バイトしかチェックしていない.

9/port/sysproc.c:
  217: long
  218: sysexec(ulong *arg)
  219: {

  267:                 /*
  268:                  * Process #! /bin/sh args ...
  269:                  */
  270:                 memmove(line, &exec, sizeof(Exec));
  271:                 if(indir || line[0]!='#' || line[1]!='!')
  272:                         error(Ebadexec);

ちなみに,UNIXはexecve(2)がシステムコールで,ライブラリコールとしていろいろなexecファミリが提供されている.Plan9のlibcにはexeclってのがあるだけ.

libc/port/execl.c:
    4: int
    5: execl(char *f, ...)
    6: {
    7: 
    8:         return exec(f, &f+1);
    9: }