2014年9月21日日曜日

スクリプトの仕組み(いくつかの実験と考察)


私はscriptをよく使うのだが、scriptが走るときに具体的に何が起こっているのかを確認したいと思って、いくつか実験してみた。
(注意!!以下は、FreeBSDでの話です。)

実験1

スクリプト:hoge.scm
#!/usr/local/bin/gosh 
(define (main args)
  (print args))
実行結果
$ aho.scm
$ hoge.scm
(./hoge.scm)
$ gosh hoge.scm
(./hoge.scm)
interpreterにscriptを渡すのと、scriptを直接実行するのとは同じようだ。

実験2a

スクリプト:hoge.echo
hoge.echo
#!/usr/bin/echo
This is echo.
実行結果
$ ./hoge.echo 1 2 3
./hoge.echo 1 2 3
$ echo ./hoge.echo 1 2 3
./hoge.echo 1 2 3

実験2b

スクリプト:hoge.echo
#!/usr/bin/echo -n
This is echo.
実行結果
$ ./hoge.echo 1 2 3
./hoge.echo 1 2 3(改行なし)
$ echo ./hoge.echo 1 2 3
./hoge.echo 1 2 3(改行あり)

実験2c

スクリプト:hoge.echo
#!/usr/bin/echo -n that is echo
This is echo.
実行結果
$ ./hoge.echo 1 2 3
-n that is echo ./hoge.echo 1 2 3
$ echo ./hoge.echo 1 2 3
./hoge.echo 1 2 3
#!の後には、好きな実行ファイルが来ていいようだ。
しかし、echoはスクリプト名を引数として受け取ることは想定していないので、
$ echo filename
の形式は意味を成さない。

実験3a

スクリプト:hoge.c -> コンパイルしたものは hoge
#include < stdio.h >

int main(int argc, char **argv){
  int i;
  for(i=0; i;< argc; i++){
    printf("argv[%d]=%s\n", i, argv[i]);
  }
  return 0;
}  
実行結果
$ ./hoge 1 2 3
argv[0]=./hoge
argv[1]=1
argv[2]=2
argv[3]=3

実験3b

スクリプト:hoge.hoge
#!./hoge
実行結果
$ ./hoge.hoge 1 2 3
argv[0]=./hoge
argv[1]=./hoge.hoge
argv[2]=1
argv[3]=2
argv[4]=3

実験3c

スクリプト:hoge.hoge
#!./hoge -s
実行結果
$ ./hoge.hoge 1 2 3
argv[0]=./hoge
argv[1]=-s
argv[2]=./hoge.hoge
argv[3]=1
argv[4]=2
argv[5]=3

実験3d

スクリプト:hoge.hoge
#!./hoge -s witch
実行結果
$ ./hoge.hoge 1 2 3
argv[0]=./hoge
argv[1]=-s witch
argv[2]=./hoge.hoge
argv[3]=1
argv[4]=2
argv[5]=3

実験4

スクリプト:hoge.awk
#!/usr/bin/awk -f
BEGIN{
    for (i=0; i < ARGC; i++){
 print "ARGV[" i "]=" ARGV[i]
    }
}
実行結果
$ hoge.awk 1 2 3
ARGV[0]=/usr/bin/awk
ARGV[1]=1
ARGV[2]=2
ARGV[3]=3

$ awk -f hoge.awk 1 2 3
ARGV[0]=awk
ARGV[1]=1
ARGV[2]=2
ARGV[3]=3

結論

スクリプト:script
#! <interpeter>  <arg>
--- 
---

$ script argv1 argv2
として、実行されると、

裏では、(forkした後に)
argv[0] = <interpreter>
argv[1] = <arg>
argv[2] = script
argv[3] = argv1
argv[4] = argv2
として、execが実行される。
(ただし、<arg>が空の時には、
argv[0] = <interpreter>
argv[1] = script
argv[2] = argv1
argv[3] = argv2
と一個ずれる。)

別の書き方をすれば、次のようになる。
execve("script", "argv1", "argv2")
↓(shellが、scriptの最初の2バイトを読んで「scriptだ!」と気づく)
あらためて、
execve("<interpreter>", "<arg>", "script", "argv1", "argv2")
する。


スクリプト中の一行目の<args>はひとまとまりとして、<interpreter>に渡される。
したがって、<args>が空白で区切られたいくつかのフレーズを含んでも正しく解釈されない。
空白を含まないオプションを一つだけ与えたときのみ、意図した動きをする。
(実験2と実験3、/usr/src/bin/echo/echo.c)

interpreterがいわゆるスクリプト言語の場合は、argv1以降だけにトリミングして渡してくれるているので、interpreterにscriptを渡すのと、scriptを直接実行するので、同じように実行することができるようだ。
(sourceコードを見たのだけれど仕組みがわからなったので、誤解しているかもしれない。)
(実験1と実験4)

今のところは、考察は以上。

環境

OS:FreeBSD 9.3-STABLE r268734

以上