linux系统编程综合练习-实现一个小型的shell程序(二)
上节minishell当中,已经初步实现了一个简单命令的解析,这节来继续对更加复杂命令进行解析,包含:输入重定向的解析、管道行的解析、输出重定向的解析以及是否有后台作业的解析,如下:
![](http://images0.cnblogs.com/i/324374/201407/172234288183865.png)
下面对其进行实现,上节中实现了对单条命令的解析,如下:
![](http://images0.cnblogs.com/i/324374/201407/201010304595575.png)
这节因为是多条,所以解析命令的实现也得重新开始写,在写之前,先列一个实现步骤:
![](http://images0.cnblogs.com/i/324374/201407/201016369594474.png)
先写一个流程伪代码,交其框架定出来,然后再去实现一个个功能函数,最后整个功能完成,这是一个比较好的编码习惯,先全局,先局部:
![](http://images0.cnblogs.com/i/324374/201407/201041225211927.png)
下面先定义未实现的函数:
![](http://images0.cnblogs.com/i/324374/201407/201051331624773.png)
![](http://images0.cnblogs.com/i/324374/201407/201052227402993.png)
然后再定义用到的全局变量:
![](http://images0.cnblogs.com/i/324374/201407/201053238655219.png)
并且在extends.h文件中进行声明:
![](http://images0.cnblogs.com/i/324374/201407/201054193185678.png)
这时,先来make一下,看这些修改能否正常编译:
![](http://images0.cnblogs.com/i/324374/201407/201055038496773.png)
从中来看,目前这个简单命令的解析框架已经搭建完毕,接下来,则是一个个函数进行实现:
void get_command(int i):获取第几条命令:
在实现解析方法之前,需要重新定义一下我们的命令数据结构,因为这一次是由多个命令组成,而不是单个命令,那要定义成一个什么样的结构呢?
![](http://images0.cnblogs.com/i/324374/201407/201502189121812.png)
所以,我们的命令数据结构需调整为:
![](http://images0.cnblogs.com/i/324374/201407/201506547406578.png)
由于现在是多条命令解析,所以cmd需要将其声明为数组:
![](http://images0.cnblogs.com/i/324374/201407/201508260375353.png)
![](http://images0.cnblogs.com/i/324374/201407/201509187711542.png)
另外,对于是cmd的初始化操作也得进行变化:
![](http://images0.cnblogs.com/i/324374/201407/201516420374956.png)
在继续编写前,咱们先来编译一下,看是否能编译过,一步一步脚印,步步为营,这样编写能减少出错的机率:
![](http://images0.cnblogs.com/i/324374/201407/201511250846818.png)
出错了,这也说明好的编码习惯,得改一点,立马来确认是否能过编译通过,一点点往上加功能,这样也会比较踏实,好了解决错误,是由于在execute_command还是执行的单条命令,所以肯定会出错,先将其注释掉:
![](http://images0.cnblogs.com/i/324374/201407/201514107569038.png)
再来编译这次就ok了,下面开始进行解析,根据解析的示例图,需要将cmdline中的命令参数提取到avline数组中,所以声明两个变量来分别指向cmdline和avline:
![](http://images0.cnblogs.com/i/324374/201407/201552474745901.png)
![](http://images0.cnblogs.com/i/324374/201407/201554182875992.png)
![](http://images0.cnblogs.com/i/324374/201407/201554482244612.png)
下面开始一步步进行解析:
![](http://images0.cnblogs.com/i/324374/201407/201558188493098.png)
![](http://images0.cnblogs.com/i/324374/201407/201622306467632.png)
也就是这一步:
![](http://images0.cnblogs.com/i/324374/201407/201623054436135.png)
![](http://images0.cnblogs.com/i/324374/201407/201629525992547.png)
其实,这个解析还是有点问题,比如命令"cat < test.txt",依照上面编写的代码来分析,当解析完cat之后,因为遇到了' ',所以j++:
![](http://images0.cnblogs.com/i/324374/201407/201635423341600.png)
再次循环:
![](http://images0.cnblogs.com/i/324374/201407/201636215684815.png)
也就是cmd[i].args[1] = ' ';而实际上只有一个cat命令,并没有第二个参数,所以需要做如下处理:
/* * 解析简单命令至cmd[i] * 提取cmdline中的命令参数到avline数组中 * 并且将COMMAND结构中的args[]中的每个指针指向这些字符串 */ void get_command(int i){ /* cat < test.txt | grep -n public > test2.txt & */ int j = 0;//代表命令中的参数个数 int inword;//是否在单词中 while(*lineptr != '\0'){ /* 去除空格 */ while(*lineptr == ' ' || *lineptr == '\t') lineptr++; /* 将第i条命令第j个参数指向avptr */ cmd[i].args[j] = avptr;
/* 提取参数 */ while(*lineptr != '\0' && *lineptr != ' ' && *lineptr != '\t' && *lineptr != '>' && *lineptr != '<' && *lineptr != '|' && *lineptr != '&' && *lineptr != '\n'){
/* 参数提取至avptr指针指向的数组 */ *avptr++ = *lineptr++; inword = 1; } *avptr++ = '\0'; switch (*lineptr)//说明命令中的一个单词解析完 { case ' ': case '\t'://表示一个命令的第一个参数已经解析完,应该继续解析其第二个参数,如:grep -n public,当解析完grep后,则会遇到空格,应该继续解析参数-n inword = 0; j++; break; case '<': case '>': case '|': case '&': case '\n'://说明命令解析结束了 if(inword == 0) cmd[i].args[j] = NULL;//这时需要将空格参数去掉return; default: /* for '\0' */ return; } } }
这时,写了这么多代码,先编译一下:
可见代码现在没有什么问题,接下来实现check函数
int check(const char *str):判断命令是否包含某个字符:
![](http://images0.cnblogs.com/i/324374/201407/212151023222529.png)
![](http://images0.cnblogs.com/i/324374/201407/212234508543731.png)
怎么来理解上面的写法呢?
![](http://images0.cnblogs.com/i/324374/201407/212252151668655.png)
理解起来有点绕,可以好好消化下,接下来实现另外一个函数
void getname(char *name):获得重定向后的文件名:
![](http://images0.cnblogs.com/i/324374/201407/212254489167683.png)
另外,在parse_command()中,需要修改一个地方,就是对于输出重定向,可以有>>,表示追加的方式,所以改变如下:
![](http://images0.cnblogs.com/i/324374/201407/212301270572415.png)
需要定义一下append全局变量:
![](http://images0.cnblogs.com/i/324374/201407/212303339948035.png)
![](http://images0.cnblogs.com/i/324374/201407/212304011829628.png)
对于现在的代码,得验证其是否是正确的,所以,在程序还没有写完之前,可以写一个函数来打印解析的值进行验证程序的合法性,如下:
![](http://images0.cnblogs.com/i/324374/201407/222116045573288.png)
具体代码实现,比较简单,这里就不多解释了:
![](http://images0.cnblogs.com/i/324374/201407/222116446192616.png)
运行来验证下:
![](http://images0.cnblogs.com/i/324374/201407/222121098383691.png)
但是,如果直接回车,看结果:
![](http://images0.cnblogs.com/i/324374/201407/222158074796154.png)
所以,在init方法中,需将全局变量全部进行一次初始化:
![](http://images0.cnblogs.com/i/324374/201407/222205297911727.png)
这时再来看:
![](http://images0.cnblogs.com/i/324374/201407/222208442601781.png)
这时,应该是代码逻辑的问题,来看下代码parse_command()确实是有问题:
![](http://images0.cnblogs.com/i/324374/201407/222219009941566.png)
怎么修复此问题呢?其实在解析命令最后前,做一个判断既可:
![](http://images0.cnblogs.com/i/324374/201407/222221457134944.png)
这时,再来试验:
![](http://images0.cnblogs.com/i/324374/201407/222224457601237.png)
好了,先做到这,下节继续完善~