编写自己的Shell解释器-5[转]
命令处理
首先是对处于“shell语法规范”中最上层的列表命令的处理。
l 列表命令的处理过程:
依次检查参数数组中的每一个参数,如果是分号(;),那么就认为分号前面的所有参数组成了一个管道命令,调用 do_pipe_cmd() 来执行对管道命令的处理。如果扫描到最后,不再有分号出现,那么把剩下的所有参数作为一个管道命令处理。
代码很简单:
static void do_list_cmd() { int i = 0; int j = 0; char* p; while(argbuf[i]) { if(strcmp(argbuf[i], ";") == 0) {// ; p = argbuf[i]; argbuf[i] = 0; do_pipe_cmd(i-j, argbuf+j); argbuf[i] = p; j = ++i; } else i++; } do_pipe_cmd(i-j, argbuf+j); }
|
接下来是对管道命令的处理。
管道命令的处理
管道是进程间通信(IPC)的一种形式,关于管道的详细解释在《unix高级环境编程》第14章:进程间通信以及《unix网络编程:第2卷:进程间通信》第4章:管道和FIFO中可以看到。
我们还是来看一个管道的例子:
[root@stevens root]# echo “hello world”|wc –c |wc –l
在这个例子中,有三个简单命令和两个管道。
第一个命令是 echo “hello world”,它在屏幕上输出 hello world。由于它后面是一个管道,因此,它并不在屏幕上输出结果,而是把它的输出重定向到管道的写入端。
第二个命令是 wc –c,它本来需要指定输入源,由于它前面是一个管道,因此它就从这个管道的读出端读数据。也就是说读到的是 hello world,wc –c 是统计读到的字符数,结果应该是12。由于它后面又出现一个管道,因此这个结果不能输出到屏幕上,而是重定向到第二个管道的写入端。
第三个命令是 wc –l。它同样从第二个管道的读出端读数据,读到的是12,然后它统计读到了几行数据,结果是1行,于是在屏幕上输出的最终结果是1。
在这个例子中,第一个命令只有一个“后”管道,第三个命令只有一个“前”管道,而第二个命令既有“前”管道,又有“后”管道。
在我们处理管道命令的do_pipe_cmd()函数中,它的处理过程是:
首先定义两个管道 prefd 和 postfd,它们分别用来保存“前”管道和“后”管道。此外,还有一个变量 prepipe 来指示“前”管道是否有效。
然后依次检查参数数组中每一个参数,如果是管道符号(|),那么就认为管道符号前面所有的参数组成了一个简单命令,并创建一个“后”管道。如果没有“前”管道(管道中第一个简单命令是没有“前”管道的),那么只传递“后”管道来调用do_simple_cmd(),否则,同时传递“前”管道和“后”管道来调用 do_simple_cmd()。
执行完以后,用“前”管道来保存当前的“后”管道,并设置“前”管道有效标识prepipe,继续往后扫描。如果扫描到最后,不再有管道符号出现,那么只传递“前”管道来调用do_simple_cmd()。
代码如下:
int i = 0, j = 0, prepipe = 0; int prefd[2], postfd[2]; char* p; while(argv[i]) { if(strcmp(argv[i], "|") == 0) { // pipe p = argv[i]; argv[i] = 0; pipe(postfd); //create the post pipe if(prepipe) do_simple_cmd(i-j, argv+j, prefd, postfd); else do_simple_cmd(i-j, argv+j, 0, postfd); argv[i] = p; prepipe = 1; prefd[0] = postfd[0]; prefd[1] = postfd[1]; j = ++i; } else i++; } if(prepipe) do_simple_cmd(i-j, argv+j, prefd, 0); else do_simple_cmd(i-j, argv+j, 0, 0); |
最后,我们分析简单命令的处理过程。