在Linux下面开发一个mini版的shell

一.shell的原理

Linux系统提供给用户的最重要的系统程序是Shell命令语言解释程序。它不属于内核部分,而是在核心之外,以用户态方式运行。
其基本功能是解释并执行用户打入的各种命令,实现用户与Linux核心的接口。系统初启后,核心为每个终端用户建立一个进程去
执行Shell解释程序,shell的简单定义就是命令行解释器。
我现在打印一下当前进程的父进程。
在这里插入图片描述
编译运行后,打印出来的就是父进程,通过ps aux来查看该父进程的状态。我们可以很明显的看到当前test对应的进程------56042进程就是bash进程,而bash就是一个具体的shell。shell会f先ork一个子进程,它最后会被替换成我们敲入指令对应的代码和数据。
在这里插入图片描述

二.整体思路

  1. 打印一个提示符,让用户输入一个指令。
  2. 解析输入的指令,找到对应的可执行程序。
  3. 创建子进程,子进程程序替换(替换成你敲入的命令),来加载可执行程序。
  4. 父进程进行进程等待,等待子进程结束。
  5. 子进程结束,父进程从wait中返回,循环执行1.

1.打印一个提示符,让用户输入一个指令。
我们知道每次输入指令的时候,都会有一个提示符,我在这里为了简便暂时将这个提示符写死,不随着用户所在路径而变化。这个部分非常的简单,一个简单的printf就可以解决问题。

printf("[zhaotiedan@localhost myshell]$");

2. 解析输入的指令,找到对应的可执行程序。
首先要读取用户输入的指令,我一开始使用的输入函数是scanf,但是它是一个遇到空格就返回的函数,这就代表假如我输入的是ls -l指令,那么这个函数就会以分两次返回,一次返回ls,一次返回-l。所以我在这里选用了gets函数,一次读一行数据。

char *strtok(char str[], const char *delim);
//str是要分解的字符串,delim是字符串中用来分解的字符

我在这里来分装一个方法Split,在里面使用strtok这个函数,将切分出来的结果依次放入一个字符串数组里面,在main函数里只需拿一个返回值来接收就ok了。

int Split(char input[],char* output[])
//input表示待切分的命令
//output表示切分结果
{
    char* p=strtok(input," ");//用空格来分解input
    int i=0;
    while(p!=NULL)
    {
        output[i]=p;
        i++;
        p=strtok(NULL," ");
    }
    output[i]=NULL;
    return 0;
}

char *argv[1024]={0};
int n=Split(command,argv);

这个时候我就能到达这样的效果了
在这里插入图片描述
那我们现在再详细的剖析一下strtok函数的执行过程。
分析得到,第一次调用strtok时,strtok返回的是指向ls(待切分指令的第一个字符串)的指针,当打印时,本来ls后面应该打印空格,但是我们可以很明显的看到是因为读取到‘\0’才调用结束的。那么,可以得出一个结论,是strtok将字符串的最后一个空格替换成了’\0’,所以strtok函数具有破坏原始字符串的功能。
第二次调用strtok时,strtok返回的是指向-的指针,而后面都会从上次切分的位置开始往后切分,所以strtok可以保存上次的切分结果。
第三次调用时,strtok返回指向/的指针
最后一次调用,srtok返回NULL;
在这里插入图片描述
总结:strtok必须循环调用,并且第一次调用和后续调用时传的参数是不一样的。并且使用的时候会导致线程不安全
strtok调用难,而且调用风险这么大,所以最科学的方法是基于boost的方法。Boost是为C++语言标准库提供扩展的一些C++程序库的总称,它是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称。像C++中的智能指针就是基于这个库来使用的。

3-5创建子进程,子进程程序替换,来加载可执行程序。

void CreateProcess(char* argv[],int n)
 {
   (void)n;
   //1.创建子进程     
   int ret=fork();    
   //2.父进程进行进程等待,子进程进行程序替换
   if(ret>0)          
   {                  
     //father         
     wait(NULL);      
   }                  
   else if(ret==0)    
   {                  
     //child          
     ret=execvp(argv[0],argv);//敲入的指令只有一个名字,没有路径,得去path中找
     //出错的判定if可省略,如果替换成功,肯定不会再执行后面这些代码
     perror("exec");  
     exit(0);         
   }                  
   else{              
     perror("fork");  
   }                                                                                
 }

三.源代码

#include <stdio.h>                                                                 
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/wait.h>
 #include <string.h>
 
 //input--待切分命令
 //output--表示切分u结果(字符串数组)
 //返回值为数组中有效的指令个数
 int Split(char input[],char* output[])
 {
   //借助strtok
   char* p=strtok(input," ");//以空格来切分,返回的是指向第一个字符串的指针
   int i=0;
   while(p!=NULL)
   {
     //字符串放入output
     output[i]=p;
     i++;
     p=strtok(NULL," ");
   }
   output[i]=NULL;//必须以空指针来结尾
   return i;                                                      
 }                                                                
                                                                  
 void CreateProcess(char* argv[],int n)                           
 {                                                                
   (void)n;                                                       
   //1.创建子进程                                                 
   int ret=fork();                                                
   //2.父进程进行进程等待,子进程进行程序替换                     
   if(ret>0)                
  {
     //father
     wait(NULL);
   }
   else if(ret==0)
   {
     //child
     ret=execvp(argv[0],argv);//敲入的指令只有一个名字,没有路径,得去path中找
     //出错的判定if可省略,如果替换成功,肯定不会再执行后面这些代码
     perror("exec");
     exit(0);
   }
   else{
     perror("fork");
   }
 }
 
 int main()
 {
   while(1)
   {
     //1.打印一个提示符,用缓冲区刷新输出到显示器上
     printf("[zhaotiedan@localhost myshell]$");
     //fflush(stdout);
     //2.用户输入一个指令,遇到回车读取结束
     char command[1024]={0};                                                        
     //scanf("%s",command);//scanf遇到空格会返回,所以不能用它
     gets(command);//一次读一行数据
     //3.解析指令,把要执行哪个程序识别出来,哪些是命令,哪些是参数
     char *argv[1024]={0};//放切分结果
     int n=Split(command,argv);
    //4.创建子进程,进行程序替换
     CreateProcess(argv,n);
   }
   return 0;
 }

四 .功能展示

  • ls

在这里插入图片描述

  • ls -l

在这里插入图片描述

四.缺陷和改进点

  1. 要做到提示符随用户当前所在路径变换而变化。
  2. 要支持cd命令,因为cd修改的是子进程的路径,对父进程没有影响。需要让父进程直接支持cd(而不是创建子进程和程序替换)
  3. 需要支持定义别名。
  4. 需要支持管道。
  5. 需要支持重定向。
发布了50 篇原创文章 · 获赞 30 · 访问量 9178

猜你喜欢

转载自blog.csdn.net/qq_42913794/article/details/103671244