PS:更新的这个版本支持重定向功能。
首先,我们来理理思路。主要的思路就是用当前进程(我们写的shell程序)创建一个子进程,然后让exec函数族中的函数用我们指定的进程映像(我们在终端输入的命令就是调用相应的进程)替换掉子进程的进程映像,子进程执行完毕后,用wait函数来接收子进程(避免产生僵尸进程),然后这个过程循环起来,就实现了一个连续的命令解释程序(my_shell)。我画一个图,方便大家理解这个过程。
以上就是我们的思路了,下面我们来开始写代码吧。。。
- 首先,我们把main函数的代码写一下,为了能够让代码清楚一点,我把复杂的代码单独拿出来讲解
int main( void )
{
char infor[1024]={0};//用来接收我们输入的命令
while(1)
{
printf("my@localhost:>>>");
scanf("%[^\n]%*c",infor);
m_Inplement(infor);//调用底层执行命令的函数
}
}
printf(“my@localhost:>>>”);
这句代码printf中你可以随意输出,只要你愿意,甚至还可以和原来的终端提示符一模一样
scanf(“%[^\n]%*c”,infor);
这个算是scanf的高级用法了吧。。。[]内部是说第一个%只接受[]内部的格式输入,^\n的意思是接收换行符以前的所有输入,输入到infor中,%*c的意思是最后再接收一个字符,也就是前面遗留下来的换行符,为什么要有一个星号呢?这是说接收并且丢弃掉这个字符,因为我们不需要将换行符放到数组infor中去。
- 然后我们来实现执行命令的函数 m_Inplement()
void m_Inplement(char *infor)
{
int argc=0;
int *argv[8];
int state=0;
int i=0;
for(;infor[i] != 0; i++)
{
if(!isspace(infor[i]) && state ==0)
{
argv[argc++]=infor+i;
state=0;
}
else if(isspace(infor[i]))
{
infor[i]=0;
state=0;
}
}
argv[argc]=NULL;
do_shell(argc,argv);
}
m_Inplement()函数主要做了两件事,第一件事,解析从终端输入的字符,并将之放到类似于main函数第二个参数的锯齿数组中,以便后面对命令进行操作。
那么首先,我们来看第一件事
1.字符串解析的思路: 采用有限状态机的思想,我们在这里定义存放终端字符的数组的两种状态,一种是处于无效字符,我们用state=0来表示这种状态;一种是处于有效字符,我们要对其字符进行解析和存放,我们用state=1来表示这种状态。
- 如果当前字符不为空白符,并且上一种状态是0,那么我们就进入字符解析模式
- 如果当前字符为空白符,那么当前就处于无效模式
2.代码解析:(以下将存放终端字符的数组称为缓冲数组,将存放命令的数组称为命令行数组)
int *argv[8];
这里我们就实现一个简易的方案,指定每次输入的命令最多只能是八个,真正的shell这里是可以无限输入的
isspace()
这个函数用来判断当前字符是否为空白符,是则返回1,否则返回0.包含在头文件< string.h>下
argv[argc++]=infor + i;
将缓冲数组中的当前命令的起始地址存放到命令数组的指定位置,然后让argc++(argc是命令的个数),这行代码也可以这样写:argv[argc++]=&infor[i];
argv[argc]=NULL;
出了上面的那个循环,说明命令已经全部放到了命令数组,然后让命令数组以NULL结尾
do_shell(argc,argv);
最后让最底层的执行命令的函数去执行终端指定的命令即可(argc是命令的个数,argv是命令)
注意:这个函数我在写的时候犯了一个很严重的低级错误,就是在定义state和argc变量的时候没有初始化,导致后面一直没有出现预知的效果!
- 最后来写指do_shell()函数
这里支持重定向功能,那么重定向功能是怎么实现的呢?首先你得了解dup2()
这d个函数
函数原型:
int dup2(int oldfd, int newfd);
参数:
第一个参数是旧的文件描述符,也就是需要替换的文件描述符;
第二个参数是新的文件描述符。
函数说明:
void do_shell(int argc,char *argv[])
{
pid_t pid =fork();//创建一个子进程
if(pid == -1)//创建失败的情况处理
{
perror("fork");
exit(1);
}
else if(pid == 0)//如果是子进程
{
int tag = 0;
int i = 0;
for(; argv[i] != NULL; i++)
{
if(strcmp(">", argv[i]) == 0)
{
tag = 1;
break;
}
argv[i] = NULL;
int newfd;
if(tag)
{
if(NULL == argv[i + 1];
{
printf("commend error!\n");
exit(1);
}
close(1);
int fd = open(argv[i + 1],O_WRONLY | O_CREAT,644);
newfd = dup2(1, fd);
}
execvp(argv[0], argv);
if(tag)
{
close(1);
dup2(newfd, 1);
}
exit(1);
}
}
else
wait(NULL);
}
到这里这个简易版的shell就写好了,是不是很简单呢?
这里还有一个小问题,这个my_shell怎么退出呢?exit? 是不是发现这样不能退出?
当然不能,因为真正的shell可以处理内置命令和外置命令,而我们写的这个只能简单的处理外置命令,并没有做内置命令的处理,所以这个my_shell只能用ctrl + c的方式来结束这个进程。
最后我在加一段代码,内置一条exit命令来结束my_shell,也顺便做一个内置命令的实例:
//用下面这段代码替换掉上面的main函数
#include<string.h>//main函数中添加代码要用到的头文件
int main( void )
{
char infor[1024]={0};
while(1)
{
printf("my@localhost:>>>");
scanf("%[^\n]%*c",infor);
if(strncmp(infor,"exit",4) == 0)
exit(0);
m_Inplement(infor);
}
}
PS:这是作者的脑力劳动成果,转载请注明出处:
https://blog.csdn.net/zanda_