编写自己的Shell解释器-3[转]

实现shell实例

程序主框架

       主程序很简单,它在做一些必要的初始化工作之后,进入这样一个循环:

u       打印提示符并等待用户输入

u       获取用户输入

u       分析用户输入

u       解释执行;

如果用户输入 logout或者 exit 之后,才退出这个循环。

用类似伪代码的形式表示如下:

while(1) {

       print_prompt();

       get_input();

       parse_input();

       if(“logout” || “exit”)

              break;

       do_cmd();

}

 

读取用户输入

如何获取用户输入?一种方法是通过 getchar() 从标准输入每次读一个字符,如果读到的字符是 ‘\n’,说明用户键入了回车键,那么就把此前读到的字符串作为用户输入的命令。

代码如下:

 

int len = 0;

int ch;

char buf[300];

 

ch = getchar();

while(len < BUFSIZ && ch != '\n') {

       buf[len++] = ch;

       ch = getchar();

}

if(len == BUFSIZ) {

       printf("command is too long\n");

       break;

}

buf[len] = '\n';

len++;

buf[len] = 0;

 

但是,我们注意到,在 bash 中,可以用“<-”和“->”键在命令行中左右移动,可以用上下键调用以前使用的命令,可以用退格键来删除一个字符,还可以用 tab 键来进行命令行补全。我们的shell如果也要支持这些功能,那么就必须对这些键进行处理。这样仅仅对用户输入的读取就非常麻烦了。

实际上,任何需要一个获取用户输入的程序,都会涉及到同样的问题,如何象bash 那样处理键盘?GNU readline 库就是专门解决这个问题的,它把对键盘的操作完全封装起来,对外只提供一个简单的调用接口。有了它,对键盘的处理就不再让人头疼了。

关于 readline 库的详细信息,可以通过 man readline 来看它的帮助页面。在我们的 shell 程序中,我是这样来使用 readline的。

 

char* line;

char prompt[200];

while(1) {

       set_prompt(prompt);

       if(!(line = readline(prompt)))

              break;

       。。。。。。

}

 

首先通过 set_prompt() 来设置要输出的提示符,然后以提示符作为参数调用 readline(),这个函数等待用户输入,并动态创建一块内存来保存用户输入的数据,可以通过返回的指针 line 得到这块内存。在每次处理完用户输入的命令之后,我们必须自己负责来释放这块内存。

有了 readline 之后,我们就可以象 bash 那样使用键盘了。

在通过 readline 获取用户输入之后,下一步就是对用户输入的命令进行分析。

 

命令行分析

对命令行的分析,实际上是一个词法分析过程。学过编译原理的朋友,都听说过 lex yacc 的大名,它们分别是词法分析和语法分析工具。Lex yacc 都有GNU的版本(open source 的思想实在是太伟大了,什么好东东都有免费的用),分别是 flex bison

所谓“工欲善其事,必先利其器”,既然有这么好的工具,那我们就不必辛辛苦苦自己进行词法分析了。对,我们要用 lex 来完成枯燥的命令行词法分析工作。

“去买本《lexyacc》(中国电力出版社)来看吧。第一次学当然稍微有点难度,不过一旦掌握了,以后再碰到类似问题,就可以多一个利器,可以节省劳动力了

在我们的这个 shell 程序中,用 flex 来完成词法分析工作。相对语法分析来说,词法分析要简单的多。由于我们只是做一个简单的 shell,因此并没有用到语法分析,而实际上在 bash 的实现代码中,就用到了语法分析和 yacc

关于 lex 的细节,在这里我就不能多说了。Lex程序,通常分为三个部分,其中进行语法分析工作的就是它的第二部分: “规则”。规则定义了在词法分析过程中,遇到什么样的情况,应该如何处理。

词法分析的思路,就是根据前面定义的“shell语法规范”来把用户输入的命令行拆解成

首先,我们要把用户输入的命令,以空白字符(tab键或者空格)分隔成一个个的参数,并把这些参数保存到一个参数数组中。但是,这其中有几种特殊情况。

一、如果遇到的字符是;”、“>”、“<”或“|,由于这些符号是管道或者列表中所用到的分隔符,因此必须把它们当作一个单独的参数。

二、以双引号(括起来的字符串要作为一个单独的参数,即使其中出现了空白字符、“;”、“>”、“<”、“|”。其实,在POSIX标准中,对引号的处理相当复杂,不仅包括双引号(),还有单引号()、反引号(`),在什么情况下,应该用什么样的引号以及对引号中的字符串应该如何解释,都有一大堆的条款。我们这里只是处理一种极简单的情况。

 

其次,如果我们遇到换行符(’\n’),那么就结束本次命令行分析。根据前面定义的 shell 语法规范,最上层的是列表命令,因此下一步是把所有的参数作为一个列表命令来处理。

posted @ 2008-12-05 21:31  aoogur  阅读(466)  评论(0编辑  收藏  举报