Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1727220
  • 博文数量: 438
  • 博客积分: 9799
  • 博客等级: 中将
  • 技术积分: 6092
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-25 17:25
文章分类

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-03-29 13:38:10

当前所有的UNIX系统都支持解释文件。这些文件是以下面形式的行开头的文本文件:
#! pathname [ optional-argument]


在惊叹号和路径名间的空格是可选的。这些解释文件最普遍是由下行开头:
#!/bin/sh


路径名通常是一个绝对路径名,因为基于它之上的特殊操作被执行(也就是说,PATH没有被使用)。这些文件的识别由内核作为处理exec系统调用的一部分 完成。被内核执行的真实文件不是解释文件,而是由解释文件第一行的路径名指定的文件。需要区分解释文件--以#!开头的一个文本文件--和解释器,它由解 释文件的第一行的路径名指定。


注意系统在一个解释文件的第一行上有一个尺寸设置。这个限置包括#!、路径名、可选参数、终止的换行符、和任何空格。


在FreeBSD 5.2.1上,这个限制为128字节。Mac OS X 10.3把这个限制扩展为512字节。Linux 2.4.22支持127字节的限制,而Solaris 9把限制定为1023字节。


让我们看一个例子,来看下当被执行的文件是一个解释文件时,内核如何处理传给exec函数的参数和解释文件第一行的可选参数。下面的程序exec了一个解释文件:



  1. #include <sys/wait.h>
  2. #include <unistd.h>
  3. #include <stdio.h>

  4. int
  5. main(void)
  6. {
  7.     pid_t pid;

  8.     if ((pid = fork()) < 0) {
  9.         printf("fork error\n");
  10.         exit(1);
  11.     } else if (pid == 0) { /* child */
  12.         if (execl("/home/tommy/bin/testinterp",
  13.                     "testinterp", "myarg1", "MY ARG2", (char*)0) < 0) {
  14.             printf("execl error\n");
  15.             exit(1);
  16.         }
  17.     }
  18.     if (waitpid(pid, NULL, 0) < 0) { /* parent */
  19.         printf("waitpid error\n");
  20.         exit(1);
  21.     }
  22.     exit(0);
  23. }


下面展示一个执行的一行的文件解释器的内容和它的运行结果:

$ cat /home/tommy/bin/testinterp
#!/home/tommy/bin/echoarg foo

$ ./a.out
argv[0]: /home/tommy/bin/echoarg
argv[1]: foo
argv[2]: /home/tommy/bin/testinterp
argv[3]: myarg1
argv[4]: MY ARG2

(这个解释器)echoarg程序只打印它命令行参数。(这个程序在7.4节)。注意当内核exec这个解释器(/home/tommy/bin /echoarg),argv[0]是解释器的路径名,argv[1]是解释文件的可先参数,而剩下的参数是路径名(/home/tommy/bin /testinterp)和在上面代码里的execl调用的第二和第三个参数(myarg1和MY ARG2)。execl调用里的arg[1]和arg[2]都往又移动了两个位置。注意内核从execl调用了接受了路径名而不是第一个参数 (testinterp),假设路径名可能包含比第一个参数更多的信息。


在解释器路径名之后的可选参数的一个普遍用法是为支持这个-f选项的程序指定这个选项。例如,一个awk程序可以执行为:
awk -f myfile
它告诉awk从myfile文件读取awk程序。


从UNIX系统V继承的系统经常包含两个版本的awk语言。在这些系统上,awk经常被称为“老awk”并对应在于版本7的原始版。相反,nawk(新 awk)包含许多升级并对应于Aho、Kernighan、和Weinberger[1988]描述的语言。这个更新的版本提供了命令行参数的访问,我们 需要它来展示下面的例子。


awk程序是POSIX在它的1003.2标准里包含的工具,它现在是SUS的基本POSIX.1规范的一部分。这个工具也是基于Aho、Kernighan,和Weinberger[1988]的语言。


Mac OS X 10.2的awk版本是基于贝尔实验室的版本,由Lucent放入公共域。FreeBSD 5.2.1和Linux 2.4.22与GNU awk(称为gawk,被链接为awk)一同发行。gawk版本遵守POSIX标准,但也包含了其它的扩展。因为更新,所以贝尔实验室的awk版本和 gawk比nawk或老awk更受欢迎。


在解释文件里使用-f选项让我们可以写出:
#!/bin awk -f
(awk program follows in the interpreter file)


例如,下面展示了一个解释文件:


  1. #!/usr/bin/awk -f
  2. BEGIN {
  3.     for (i = 0; i < ARGC; i++)
  4.         printf "ARGV[%d] = %s\n", i, ARGV[i]
  5.     exit
  6. }


运行结果为:
$ ./awkexample file1 FILENAME2 f3ARGV[0] = awk
ARGV[1] = file1
ARGV[2] = FILENAME2
ARGV[3] = f3

当/usr/bin/awk被执行时,它的命令行参数为
/usr/bin/awk -f /home/tommy/bin/akwexample file1 FILENAME2 f3


解释文件(/home/tommy/bin/akwexample)的路径名被传入给解释器。这个(我们在shell里输入的)路径名的文件名部分并不 够,因为解释器(这个例子为/usr/bin/awk)并不会使用PATH变量来定位文件。当它读取解释文件时,awk忽略第一行,因为井号是awk的注 释字符。


我们可以用以下命令验证这些命令行参数:


$ /bin/su
密码:
# mv /usr/bin/awk /usr/bin/awk.save
# cp /home/tommy/bin/echoarg /usr/bin/awk
# suspend <--挂起su
[1]+  已停止               /bin/su


$ ./awkexample file1 FILENAME2 f3
argv[0]: /usr/bin/awk
argv[1]: -f
argv[2]: ./awkexample
argv[3]: file1
argv[4]: FILENAME2
argv[5]: f3
tommy@tommy-Calistoga-ICH7M-Chipset:~/code/unix$ fg  <--恢复su
/bin/su
# mv /usr/bin/awk.save /usr/bin/awk
root@tommy-Calistoga-ICH7M-Chipset:/home/tommy/code/unix# exit <--退出su
exit


在这个例子里,解释器的-f选项是必需的。正我们说过的,这告诉awk哪里去查找awk程序。如果我们从解释文件里删除了-f选项,当我们尝试运行它时通 常会导致一个错误信息。错误的确切信息取决于解释文件在哪里存储和剩余的参数是否表示存在的文件。这是因为在这种情况下命令行参数是:
/bin/awk /usr/local/bin/awkexample file1 FILENAME2 f3


而awk作为一个awk程序尝试去解释字符串/usr/local/bin/awkexample。如果我们不能传递至少一个可选参数(这种情况下是-f)给解释器的话,那么这些解释器文件只能和shell使用。


解释器文件是必需的吗?不算是。它们以内核的花费为用户赚取效率(因为是内核识别这些文件)。解释器文件因为以下原因而有用:


1、它们隐藏了特定程序是某些语言的脚本。例如,为了执行上面的代码,我们仅说:awkexample optional-arguments而不需要知道程序其实是一个awk脚本,不然我们必须这样执行:awk -f awkexample optional-arguments。


解释器脚本获得了效率。再考虑前一个例子,我们可以仍然隐藏程序是一个awk脚本,通过把它包装在一个外壳脚本里:


ark 'BEGIN {
    for (i = 0; i < ARGC; i++)
        printf "ARGV[%d] = %s\n", i, ARGV[i];
    exit
}' $*


这个程序的问题是它需要更多的工作。首先,外壳读取命令并尝试execlp文件名。因为外壳脚本是一个可执行文件,但不是一个机器可执行程序,一个错误返 回,而execlp假设文件是一个外壳脚本(它确实是)。然后/bin/sh被执行,用外壳脚本的文件名作为它的参数。这个外壳正确地运行我们的脚本,但 是为了运行awk程序,外壳执行了一个fork、exec和wait。因此,有更多的开销用在用一个外壳脚本代替一个解释器脚本。


2、解释器脚本让我们使用不是/bin/sh的脚本。当它发现一个可执行文件不是机器指令,那么execlp必须选择一个外壳来调用,而它通常使用/bin/sh。然而,使用一个解释器脚本,我们可以简单地写:


#!/bin/sh
(C shell script follows in the interpreter file)


再次申明,我们可以把它们包装在一个/bin/sh脚本里(来调用C外壳),然而,正如我们已经描述过的,需要更多的花销。


正如我们展示的,如果这三个外壳和awk不使用井号作为它们的注释字符,这些没有一个可以工作。

阅读(877) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~