曲径通幽论坛

标题: 调用脚本解释器过程的简单分析 [打印本页]

作者: beyes    时间: 2011-6-15 16:23
标题: 调用脚本解释器过程的简单分析
在脚本文件的第 1 行往往会看到:
#!/bin/bash
这里 bash 就是我们要求用来解析当前脚本的脚本解释器,对脚本解释器的调用由内核使调用 exec 函数的进程来执行。下面程序使用 exec 函数来模拟脚本文件被执行的过程。

程序代码如下:
[C++] 纯文本查看 复制代码
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

void err_msg (const char *msg)
{
    perror (msg);
    exit (EXIT_FAILURE);
}

int main(void)
{
    pid_t    pid;
    if ((pid = fork()) < 0) {
        err_msg ("fork error");
    } else if (pid == 0) {
        if (execl("/home/beyes/shell/temp.sh", "temp.sh", "arg1", "arg2", (char *)0) < 0)
            err_msg("execl error");
    }
    if (waitpid(pid, NULL, 0) < 0)
        err_msg("waitpid error");

    exit (EXIT_SUCCESS);
}

在上面程序中,我们使用 execl() 函数来执行位于 /home/beyes/shell/ 下的 temp.sh 脚本文件,并向该脚本文件传递了两个命令行参数 arg1 和 arg2 。

temp.sh 文件的内容如下:
[Bash shell] 纯文本查看 复制代码
#!/home/beyes/bin/tinyshell -x

echo hello tinyshell

echo hello execl


date

在上面的脚本中,自定义了一个名为 tinyshell 的脚本解析程序,后面还给它加了个 -x 参数 (在 bash 里,-x 参数是用来辅助调试用的);这里,并不打算处理这个 -x 参数以及传递进来的 arg1 和 arg2 这两个命令行参数。

execl() 要求解析该脚本文件,当 execl() 执行后,内核会将此任务交给这个脚本文件第一行所指定的解析器 tinyshell 来完成。这个简单的脚本解析器实现如下:
[C++] 纯文本查看 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int i;
    FILE *fp;
    char combuf[512];
    char *pbuf = combuf;
    int c;

    for (i = 0; i < argc; i++)
        printf ("argv[%d] = %s\n", i, argv);

    printf ("------------interpret script begin------------\n");

    fp = fopen(argv[2], "r");

    if ((unsigned char)fgetc(fp) == '#') {
        while ((unsigned char)(c = fgetc(fp)) != '\n')
            continue;
    }
    while (!feof(fp)) {
                fseek (fp, -1, SEEK_CUR);
        while ((unsigned char)(c = fgetc(fp)) == '\n')
            continue;
        
        fseek (fp, -1, SEEK_CUR);
        while ((unsigned char)(c = fgetc(fp)) != '\n')
            *pbuf++ = (unsigned char)c;

        *pbuf = '\0';
        system(combuf);
        pbuf = combuf;
        fgetc(fp);
    }
    fclose(fp);
    return 0;
}

说明
一开始将命令行参数打印出来,然后下面主要利用 system() 函数来执行这些命令。

需要注意的是:
由于 fgetc() 返回的是一个由 unsigned char 类型扩展的整型值,由于我们这里读取的都是普通的字符类型,所以在返回值前面我们也用 unsigned char 进行了类型的转换。

在 23 行的 if 判断里,主要是去掉第一行内容,即  #!/home/beyes/bin/tinyshell -x ,这一行内容不是命令,当然也无需执行。

接下来,用 feof(fp) 函数一直跟踪整个命令内容直到文件末尾,feof() 函数是通过判断文件流指针的当前位置来判断是否已达文件末尾的。关于 feof() 函数可参考:http://www.groad.net/bbs/read.php?tid-931.html


[C++] 纯文本查看 复制代码

while ((unsigned char)(c = fgetc(fp)) == '\n')
            continue;

里,目的是过滤掉脚本中每一行命令间的杭欢空白。

fseek() 函数用来调整文件流指针的位置,这里往前挪 1 个字节,目的是正确读取到命令,否则可能会漏掉命令字符串,比如读取到 echo 命令时,会被读到 cho 而漏掉第一个字母 e 。

fseek() 函数的详细讨论可参考:http://www.groad.net/bbs/read.php?tid-566.html

在 pbuf = combuf; 这条语句下的 fgetc(fp); 是用来读取可能的 EOF 字符。如果还没遇到 EOF ,那么再一次 while 循环开始时也要用 fseek() 来调整一下指针位置,否则也有可能造成字符读取遗漏。

运行输出
beyes@debian:~/C/misc$ ./execsh
argv[0] = /home/beyes/bin/tinyshell
argv[1] = -x
argv[2] = /home/beyes/shell/temp.sh
argv[3] = arg1
argv[4] = arg2
------------interpret script begin------------
hello tinyshell
hello execl
Wed Jun 15 04:19:35 EDT 2011
这只是一个相当简单的且简陋的脚本解析器,通过它演示了 shell 脚本是如何被解释运行的。




欢迎光临 曲径通幽论坛 (http://www.groad.net/bbs/) Powered by Discuz! X3.2