解释器文件

    解释器文件是一种文本文件,其起始行的形式是:
        #! pathname [optional-argument]
    其中感叹号和 pathname 之间的空格是可选的。pathname 通常是绝对路径名,对它不进行什么特殊处理(不使用 PATH 进行路径搜索),它后面的参数(如果有)无论多少都会被作为一个参数来对待。对这种文件的识别是由内核作为 exec 系统调用处理的一部分来完成的。内核使调用 exec 函数的进程实际执行的并不是该解释器文件,而是 pathname 所指定的解释器。
    很多系统对解释器文件的第一行都有长度限制,比如 Linux 3.2.0 中该限制为 128 字节。
    下面这个程序 interFileDemo.out 调用 exec 执行一个解释器文件,演示了当被执行的文件是个解释器文件时,内核如何处理 exec 函数的参数以及该解释器文件第一行的可选参数。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void){
	pid_t	pid;
	if((pid=fork()) < 0){
		printf("fork error\n");
		exit(2);
	}
	if(pid == 0){
		if(execl("./testInterp", "testInterp", "arg1", "arg2", (char *)0) < 0){
			printf("execl error\n");
			exit(2);
		}
	}
	if(waitpid(pid, NULL, 0) < 0){
		printf("waitpid error\n");
		exit(2);
	}
	exit(0);
}

    这里的解释器文件“./testInterp”只有一行内容:
        /home/me/bin/echoarg foo bar
    而它所指定的解释器 echoarg 的源代码如下。
#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(0);
}

    运行结果如下(这 3 个文件都需要有执行权限):
$ ./interFileDemo.out 
argv[0]: /home/me/bin/echoarg
argv[1]: foo bar             # 这里把它们都合并成一个参数了
argv[2]: ./testInterp        # 取的是 exec 调用中的 pathname,而非 arg0
argv[3]: arg1
argv[4]: arg2

    在解释器 pathname 后可跟随可选参数,比如 awk(1) 程序支持“awk -f myfile”,它告诉 awk 从文件 myfile 中读 awk 程序,在解释器文件中可以写成:
        #!/bin/awk -f
    比如下面这个关于 awk 的解释器文件程序 awkexample。
#!/usr/bin/awk -f
# Note: on Solaris, use nawk instead
BEGIN{
	for(i=0; i<ARGC; i++)
		printf "ARGV[%d] = %s\n", i, ARGV[i]
	exit
}

    执行该程序得到如下结果:
$ ./awkexample file filename2 f3
ARGV[0] = awk
ARGV[1] = file
ARGV[2] = filename2
ARGV[3] = f3

    执行 /bin/awk 时,其命令行参数是:
        /bin/awk -f ./awkexample file filename2 f3
    可用下列命令验证上述命令行参数。
$ /bin/su
密码:
# mv /usr/bin/awk /usr/bin/awk.save
# cp ./echoarg /usr/bin/awk
# suspend                                # 用作业控制挂起超级用户 shell
[1]+  Stopped                 /bin/su
$
$ ./awkexample file filename2 f3         # 这时的 /usr/bin/awk 实际上是 echoarg
argv[0]: /usr/bin/awk
argv[1]: -f
argv[2]: ./awkexample
argv[3]: file
argv[4]: filename2
argv[5]: f3
$
$ fg
/bin/su
$ mv -f /usr/bin/awk.save /usr/bin/awk
# exit
$

    注意,如果不能向解释器传递可选参数,那么该解释器文件只有对 shell 才是有用的。
    解释器文件可使用户得到效率方面的好处,代价是内核的额外开销(因为是内核识别接解释器文件)。由于下述理由,解释器文件是有用的。
    1、隐藏程序的编写语言。比如要执行上面的 awkexample 程序,只需要使用命令:
         ./awkexample optional-arguments
       而没必要知道该程序是一个 awk 脚本,否则就需要以下列方式执行程序:
         awk -f awkexample optional-arguments
    2、解释器脚本在效率方面也提供了好处。假设将上面的 awkexample 代码放在一个普通 shell 脚本中:
awk 'BEGIN{
	for(i=0; i<ARGC; i++)
		printf "ARGV[%d] = %s\n", i, ARGV[i]
	exit
}' $*

       这种方法将会做更多的工作。首先,shell 读此命令,然后试图 execlp 此文件名。因为 shell 脚本是一个可执行文件,但却不是机器可执行的,于是返回一个错误,execlp 就认为该文件是一个 shell 脚本(它实际上就是),然后执行 /bin/sh,并以该 shell 脚本的路径名作为其参数。尽管 shell 能正确地执行这个 shell 脚本,但是为了运行 awk 程序,它会调用 fork、exec 和 wait,所以用一个 shell 脚本代替解释器脚本需要更多的开销。
    3、解释器脚本使我们可以使用 /bin/sh 以外的其他 shell 来编写 shell 脚本。当 execlp 找到一个非机器可执行的可执行文件时,它总是调用 /bin/sh 来解释执行该文件。而用解释器脚本则可简单地使用“#!/bin/csh”之类的来代替。

猜你喜欢

转载自aisxyz.iteye.com/blog/2392770