带你从零编写shell解释器

1.理解命令行解释器

命令行解释器本质上就是一个 能解析我们输入的命令 并创建子进程进行进程程序替换去执行命令的一个进程

命令行解释器

因此 只要我们熟练掌握了进程创建和进程等待,实现一个基础点的命令行解释器就信手拈来了!

大思路 :1.  解析标准输入的字符串 

                2. 分析出指令是否是必须命令行解释器自己执行的

                3.创建子进程 子进程进行程序替换执行用户要求的指令 父进程进行进程等待,等待子进程结束。

                4 回到步骤一

2代码实现

我们喜欢程序能一直执行,不停的解析执行命令 所以总体应该是一个死循环

             

接下来第一步 :打印提示符

 

 这些信息分别对应 [用户名 内核版本 当前工作目录] 前两个直接是环境变量的内容 第三个需要稍做处理 代码如下

   8 #define NUM 1024
    9 #define SUB 128
   10 
   11 char cmd_str[1024];//用于用户输入指令
   12 char SUB_str[SUB];//用于分离指令和携带的选项
   13 
   14 int main(){
   15    //获取提示符信息
   16     char pwd[SUB];//用于获取当前路径
   17     memset(pwd,0,SUB);
   18     strcpy(pwd,getenv("PWD"));
   19     char*end=strtok(pwd,"/");
   20     char*cur_dir=0;//切割出当前目录
   21     while(end=strtok(NULL,"/")){
   22       cur_dir=end;
   23      }                                   
   24     while(1){ 
   25      //打印提示符
   26      printf("[%s@%s %s]$ ",getenv("USER"),getenv("HOSTNAME"),cur_dir);
   27      fflush(stdout);
   28      //用户输入指令
   29                                                                                                                                                                
   30    return 0;//测试时先跳出            
   31   }                                   
   32 }  

 效果:

第二步:读取命令 

代码:

    1 #include<unistd.h>
    2 #include<stdio.h>
    3 #include<stdlib.h>
    4 #include<string.h>
    5 #include<sys/wait.h>
    6 #include<sys/types.h>
    7 
    8 #define NUM 1024
    9 #define SUB 128
   10 
   11 char cmd_str[1024];//用于用户输入指令
   12 char sub_str[SUB];//用于分离指令和携带的选项
   13 
   14 int main(){
   15    //获取提示符信息
   16     char pwd[SUB];//用于获取当前路径
   17     memset(pwd,0,SUB);
   18     strcpy(pwd,getenv("PWD"));
   19     char*end=strtok(pwd,"/");
   20     char*cur_dir=0;//切割出当前目录
W> 21     while(end=strtok(NULL,"/")){
   22       cur_dir=end;
   23      }
   24     while(1){
   25      //打印提示符
   26      printf("[%s@%s %s]$ ",getenv("USER"),getenv("HOSTNAME"),cur_dir);                                                                                         
   27      fflush(stdout);
   28      //用户输入指令
   29      memset(cmd_str,0,NUM);//每次输入前清空上一条指令
   30      fgets(cmd_str,NUM,stdin);//可能携带选项 所以需要按行读入 不能用scanf
   31      cmd_str[strlen(cmd_str)-1]=0;//将回车去掉
   32      printf("%s\n",cmd_str);//测试
   33 
   34   }
   35 }

效果:

第三步 分离指令 和 携带选项

 代码:

        更拿到当前目录操作类型 分离字串 效果就不演示了

   28      //用户输入指令
   29      memset(cmd_str,0,NUM);//每次输入前清空上一条指令
   30      fgets(cmd_str,NUM,stdin);//可能携带选项 所以需要按行读入 不能用scanf
   31      cmd_str[strlen(cmd_str)-1]=0;//将回车去掉
   32      //分离指令和选项
   33      sub_str[0]=strtok(cmd_str," ");//拿到指令
   34      for(int i=1;sub_str[i]=strtok(NULL," ");i++){;}//拿到选项

 第四步:对内建指令做特殊处理

针对一些要对命令行解释器进程本身做操作的指令 我们不创建子进程直接进行系统调用完成


   10 #define ENV_BUFFER 1024
   13 char env_buffer[ENV_BUFFER];//用于储存要增加的环境变量
  
   32      //分离指令和选项
   33      sub_str[0]=strtok(cmd_str," ");//拿到指令
   34      for(int i=1;sub_str[i]=strtok(NULL," ");i++){;}//拿到选项
   35      //针对内建指令
   36      if(!strcmp(sub_str[0],"cd")&&sub_str[1]){ //cd
   37        chdir(sub_str[1]);
   38        continue;                                                                                                                                               
   39      }
   43      if(!strcmp(sub_str[0],"export")&&sub_str[1]){//增加环境变量
   44        strcpy(env_buffer,sub_str[1]);//因为每次sub_str会被修改 
   45        putenv(env_buffer);//环境变量需要一块独立的空间
   46        continue;
   47      }

         //...就不一一实现了

第五步:创建子进程进行程序替换执行指令 父进程阻塞等待

这里就是进程替换和进程等待的实践运用,也是这个程序的核心代码

 代码:

      //...就不一一实现了
   45      //创建子进程
   46      pid_t id =fork();
   47      if(!id){
   48        //child
   49        //程序替换
   50        execvp(sub_str[0],sub_str);
   51        //如果指令有误才会执行下面的语句
   52        printf("-myshell: %s: command not found\n",sub_str[0]);
   53        continue;   
   54      }             
   55      else if(id>0){
   56        //father    
   57        int status=0;
   58        int ret=0;  
   59        ret =waitpid(id,&status,0);//阻塞等待子进程
   60        if(ret){
   61           //调试信息
   62 //         printf("等待成功 ");
   63 //         if(WIFEXITED(status))
   64 //            printf( "退出码:%d\n",(status>>8)&0xFF);
   65 //         else 
   66 //            printf("信号:%d\n",status&0x7F);
   67 //
   68 //         }
   69 //       else{
   70 //           printf("等待失败!\n");
   71 //           exit(1);
   72 //         }
   73       }
   74        else{
   75           printf("创建进程失败!\n");
   76           exit(2);
   77        } 
   78   }   

效果:基本上就已经大功告成了!

 第六步 完善细节

这样实现的ls没有颜色 是因为没有带任何选项 而bash的ls是默认带了颜色选项的

bash的ls
​​​​
我们的 ls

解决方式就是对ls特殊处理

代码:

   32      //分离指令和选项
   33      sub_str[0]=strtok(cmd_str," ");//拿到指令
   34      int i=1;//对ls 等指令增加默认选项
   35      if(!strcmp(sub_str[0],"ls"))
   36           sub_str[i++]="--color=auto";

结果:

结尾 完整代码

欢迎留言交流

完整代码奉上:myshell/myshell.c · leyi_999/Linux 学习日常代码 - Gitee.comhttps://gitee.com/leyi999/linux-learningcode/blob/c72e507560ad2c32eda2d8b48501568d9ab1f0b0/myshell/myshell.c

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值