#! 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”之类的来代替。