解释器、解释器文件
1. 什么是解释器文件
解释器文件是文本文件,它起始行的格式为:
#! pathname [optional-argument]
感叹号和pathname之间的空格是可选的。常见的解释器文件:
#!/bin/sh ...
pathname通常是绝对路径,不使用$PATH进行路径搜索。对解释器文件的识别通常由内核作为exec系统调用的一部分来完成。内核使得调用exec函数的进程所要执行的程序不是该解释器文件本身,而是该解释器文件第一行中pathname所指定的文件。
就是说,如果execlp或者execvp使用路径前缀中的一个找到了一个可执行文件,但是该文件不是由链接编辑器产生的可执行文件(二进制可执行文件),(如果该文件的第一行没有按照上面的方式指出pathname等)则认为该文件是一个shell脚本,试着调用/bin/sh,并以filename作为shell输入。
注意以下区别,
- 解释器文件: 以#!开头的文本文件
- 解释器:解释器文件第一行pathname所指定的程序
解释器文件的第一行有一定的长度限制,例如Linux为127字节。
2. 命令行参数
假设解释器文件/dir/foo.sh的内容为,
#!/bin/sh param1 ... <1> <2>
而调用execl执行这个解释器文件的C代码为,
execl("/dir/foo.sh", "foo.sh", "myarg1", "MY ARG2", NULL); <A> <B> <C> <D> argv[0] argv[1] argv[2]
那么,新程序(/bin/sh)中的实际的参数分别为:
argv[0]: /bin/sh <1> argv[1]: param1 <2> argv[2]: /dir/foo.sh <A> argv[3]: myarg1 <C> argv[4]: MY ARG2 <D>
除了<1>和<2>,内核用execl的pathname(<A>)代替了原来的argv[0](<B>),这是因为pathname一般含更有用的信息。
3. 实例
有这样一个解释器文件
#!/bin/awk -f BEGIN { for (i = 0; i < ARGC; i++) printf("ARGV[%d] = %s\n", i, ARGV[i]) exit }
如果在命令行中执行“awkexample file1 FILENAME2 f3”,结果如下:
$ awkexample file1 FILENAME2 f3 ARGV[0] = awk ARGV[1] = file1 ARGV[2] = FILENAME2 ARGV[3] = f3
那么Shell会这样调用exec,
exec("/usr/local/bin/awkexample", "awkexample", "file1", "FILENAME2", "f3", NULL);
于是新程序的命令行参数相当于,
/bin/awk -f /usr/local/bin/awkexample file1 FILENAME2 f3
4. 为什么使用解释器文件
解释器文件能得到效率方面的好处,但代价是内核的额外开销(因为识别解释器文件的是内核)。以下是使用解释器文件的理由:
- 有些程序是使用某种脚本语言编写的脚本,而解释器文件隐藏了这一事实。
- 解释器文件使我们可以使用除/bin/sh以外的shell来执行shell脚本。
5. 注意事项
- #!这一行的长度有一定的限制,linux为127,其他的系统最少的为63。
- 别在选项之后放任何空白,空白也会跟着选项一起传给被引用的程序。
- 解释器需要完整路径名。以避免可移植问题。
6. 参考资料
<apue> [8.10 exec函数];[8.12 解释器文件]
<sed & awk> [10.9 Invoking awk Using the #! Syntax]
<shell脚本学习指南> [2.4 自给自足的脚本:位于第一行的#!]