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

理系学生日記

おまえはいつまで学生気分なのか

忍者TOOLS

Shebang が interpreter を呼び出す仕組み

ぼくは参加してないんだけどシェル講習会みたいなのがあるっぽくて、そこで Shebang の話を 60 分するらしいんだけど、Shebang の話っていったって何を話すんだろう。。。
Shebang はご存知の通り、スクリプトの先頭に書くこんな一行です。

#! interpreter [args]

interpreter は /bin/bash とか、/usr/bin/perl とかですね。

で、/bin/bash って書いておくと、そのスクリプトbash で実行されて、/usr/bin/perl って書いておくと perl で実行されるんだけど、この実行を制御しているのは基本的には execve です。
execve のプロトタイプ宣言は以下のようになっていて、意味は引数名の通り。

     int
     execve(const char *path, char *const argv[], char *const envp[]);

で、この第一引数の path に Shebang 行を含むスクリプトのパスを指定した場合、面白いことが起こる。これが、シェルが実行している方法なはず。

実験

実験ていうか、man に書いてあったりするんだけど、一応やってみます。
実験の目的は、「Shebang を通して、どのように interpreter が実行されるのか」を確かめることです。

材料準備

まず、単純に execve を呼び出すプログラム call_execve を作る。

/* call_execve.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main( int argc, char **argv ) {
  char *newargv[]  = { NULL, "arg1", "arg2", "arg3" };
  char *empty[]    = { NULL };
  newargv[0] = argv[1];

  execve( argv[1], newargv, empty );
  perror( "execve" );
  exit( EXIT_FAILURE );
}

次に、与えられた引数を出力するだけの echo を作る。

/* echo.c */
#include <stdio.h>
#include <stdlib.h>

int main( int argc, char **argv ) {
  int i;
  for ( i = 0; i < argc; ++i ) {
    printf( "argv[%d] = %s\n", i, argv[i] );
  }
  exit( EXIT_SUCCESS );
}

で、実際に Shebang を持ったスクリプトを作る。

#!./echo myecho-arg

# do nothing

以上の材料を素にして、実験してみましょう。

結果

さっさと結果をさらすと以下のとおり。何をやっているかというと、execve に対して、Shebang を含むスクリプトのパスを喰わせているだけです。

mbp:~/work/test/execve% ./call_execve ./script.sh
argv[0] = ./echo
argv[1] = myecho-arg1
argv[2] = myecho-arg2
argv[3] = ./script.sh
argv[4] = arg1
argv[5] = arg2
argv[6] = arg3

argv[0] が "./echo" ですから、最終的に実際に実行されたのが ./echo であることがわかります。また、echo に渡っている引数の順序も合わせて考えると、execve を通した場合の実行は

$ [Shebang に書いたインタプリタ] [Shebang に書いた引数] [Shebang が書かれているファイル名] [execve に渡した引数]

という形で行われます。例えば、script.pl のShebang 行が "#!/usr/bin/perl -w" だったとすると、

$ /usr/bin/perl -w script.pl

という形で実行が為されます。結局、execve が Shebang を parse し、よしなに並べ替えてくれるって理解で良いと思います。