【linux】实现shell-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

自我名言只有努力才能追逐梦想只有努力才不会欺骗自己。在这里插入图片描述
喜欢的点赞收藏关注一下把在这里插入图片描述
如果发现内容有不对的地方欢迎在评论区批评指正这是对我最大的鼓励

前面学过了进程创建/终止/等待/替换。现在根据所学的内容实现一个简易版的shell。

//头文件
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>  
  5 #include<sys/wait.h>  
  6 #include<string.h>

1.提示符

这是我们在命令行上首先出现的内容。
在这里插入图片描述
注意在命令行输入指令是在同一行的。

int main()  
{
     //这里也可以使用环境变量来获取这些内容
     printf("用户名@主机名 当前路径#"); 
	 //这里printf后面不能带\n但是要把内容打印到显示器因此必须刷新缓冲区。
     fflush(stdout);                                                                                                                                                                                                                                                                                                        
     return 0;                                                                                                                                                
 }  

2.命令行参数

在这里插入图片描述

前面说过ls -a -l是命令行参数是一个整体的字符串分割层一个个字符串传到main函数中。

在这里插入图片描述
因此我们需要把输入的字符串也要分割成一个个字符串。

2.1输入指令

    7 #define NUM 1024
    8 #define OPT_NUM 64
    9 //全局变量
   10 char lineCommand[NUM];//存放输入的指令字符串
   11 char* myargv[OPT_NUM];//指针数组存放分割的一个个字符串
   12 

这里使用fgets函数把输入流放在指定数组。

在这里插入图片描述

    9 #define NUM 1024
   10 #define OPT_NUM 64
   11 
   12 char lineCommand[NUM];
   13 char* myargv[OPT_NUM];//指针数组存放分割的一个个字符串
   14 
   15 int main()
   16 {
   17     //这里也可以使用环境变量来获取这些内容
   18     printf("用户名@主机名 当前路径# ");
   19     fflush(stdout);
   20     //"ls -a -l"-----> "ls" "-a" "-l"
   21     //这里减1是为了极端情况下放\0
   22     char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
   23     assert(s);
   24     //测试一下                                                                                                                                                 
   25     printf("test:%s\n",lineCommand);
   26 
   27     return 0;
   28 }


在这里插入图片描述

发现这里空了一格。这是因为我们输入指令的时候最后一次肯定敲的是\n因此这里我们消除一下\n。

    9 #define NUM 1024
   10 #define OPT_NUM 64
   11 
   12 char lineCommand[NUM];
   13 char* myargv[OPT_NUM];//指针数组存放分割的一个个字符串
   14 
   15 int main()
   16 {
   17     //这里也可以使用环境变量来获取这些内容
   18     printf("用户名@主机名 当前路径# ");
   19     fflush(stdout);
   20     //"ls -a -l"-----> "ls" "-a" "-l"
   21     //这里减1是为了极端情况下放\0
W> 22     char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
   23     assert(s);
   24     //清除最后一个\n  abc\n                                                                                                                                    
   25     lineCommand[strlen(lineCommand)-1]=0;                                                                                                    
   26     //测试一下                                                                                                                               
   27     printf("test:%s\n",lineCommand);                                                                                                         
   28                                                                                                                                              
   29     return 0;                                                                                                                                
   30 }                  

在这里插入图片描述

2.2分割指令

在C语言的时候学过一个分割字符串的函数strtok

在这里插入图片描述

   28     //这里以空格为分隔符
   29     myargv[0]=strtok(lineCommand," ");
   30     //分割的是同一个字符串下一次第一个参数就置为NULL就可以了
   31     // myargv[1]=strtok(NULL,"");
   32     //这里需要实现循环注意strtok到字符串结束会返回NULL,myargv[end]=NULL                                                                                    
   33     int i=1;
   34     while(myargv[i++]=strtok(NULL," "));

   36 //条件编译测试分割是否成功
   37 #ifdef DEBUG
   38     for(int i=0;myargv[i];++i)
   39     {
   40         printf("myargv[%d]:%s\n",i,myargv[i]);
   41     }
   42 #endif    
  1 myshell:myshell.c
  2     gcc -o $@ $^ -std=c99 -DDEBUG //定义宏  不需要就前面加个#注释掉                                                                                                                               
  3 
  4 .PHONY:clean
  5 clean:
  6     rm -f myshell

在这里插入图片描述

3.创建子进程执行指令

前面我们学了6个替换函数。这里我们选择execvp最合适。

在这里插入图片描述

   44     //创建子进程
   45     pid_t id = fork();
   46     assert(id != -1);
   47     if(id == 0)
   48     {
   49         //子进程
   50         execvp(myargv[0],myargv);    
   51     }
   52     //父进程
   53     //这里先不关心退出码
   54     waitpid(id,NULL,0);

在这里插入图片描述

但是这里只能实现一次因此需要把整体循环起来。

	1#include<stdio.h>
    2 #include<stdlib.h>
    3 #include<unistd.h>
    4 #include<assert.h>
    5 #include<sys/types.h>
    6 #include<sys/wait.h>
    7 #include<string.h>
    8 
    9 #define NUM 1024
   10 #define OPT_NUM 64
   11 
   12 char lineCommand[NUM];
   13 char* myargv[OPT_NUM];//指针数组存放分割的一个个字符串
   14 
   15 int main()
   16 {
   17     while(1)
   18     {
   19          //这里也可以使用环境变量来获取这些内容
   20          printf("用户名@主机名 当前路径# "); 
   21          fflush(stdout);
   22          //"ls -a -l"-----> "ls" "-a" "-l"
   23          //这里减1是为了极端情况下放\0
   24          char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
   25          assert(s);
   26          //清除最后一个\n  abc\n
   27          lineCommand[strlen(lineCommand)-1]=0;
   28          //测试一下
   29          //printf("test:%s\n",lineCommand);
   30          //这里以空格为分隔符
   31          myargv[0]=strtok(lineCommand," ");
   32          //分割的是同一个字符串下一次第一个参数就置为NULL就可以了
   33          // myargv[1]=strtok(NULL,"");                                                                                                                         
   34          //这里需要实现循环注意strtok到字符串结束会返回NULL,myargv[end]=NULL
   35          int i=1;
   36          while(myargv[i++]=strtok(NULL," "));
   37 
   38 //测试分割是否成功
   39#ifdef DEBUG
   40          for(int i=0;myargv[i];++i)
   41          {
   42              printf("myargv[%d]:%s\n",i,myargv[i]);
   43          }
   44 #endif
   45 
   46          //创建子进程
   47          pid_t id = fork();
   48          assert(id != -1);
   49          if(id == 0)
   50          {
   51              //子进程
   52              execvp(myargv[0],myargv);    
   53          }
   54          //父进程
   55          //这里先不关心退出码
   56          waitpid(id,NULL,0);
   57     }
   58     return 0;
   59 }

在这里插入图片描述

4.三个细节

4.1ls可执行程序问题

在这里插入图片描述
自己实现的shell可执行程序没有颜色

   36          //这里对ls特殊处理                                                                                                                                
   37          if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
   38          {
   39              myargv[i++]=(char*)"--color=auto";                                                                                                                
   40          }  

在这里插入图片描述

4.2切换路径问题

在这里插入图片描述
我们发现当我切换到上层路径时发现当前路径没有变。这是为什么呢

什么是当前路径
在前面我们学过一个查看进程的命令ls /proc

在这里插入图片描述
exe----> 是当前进程执行的是磁盘路径下哪一个程序
cmd----> 是当前进程的工作目录

什么是当前路径默认是你在那个路径下把程序跑起来。本质就是当前进程的工作目录。

当前路径知道了那为什么自己写的shellcd的时候路径没有变化呢

子进程也有自己的工作目录默认和父进程一样。fork之后子进程执行cd命令更改的是子进程的目录子进程执行完毕之后被回收了。继续使用的是父进程。然后再创建子进程。默认和父进程工作目录一样这时执行pwd命令所以当前目录没有变。

如果就想更改当前工作目录系统提供chdir函数。
在这里插入图片描述

   1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 
  5 int main()
  6 {
  7     //想让工作目录是谁就把谁的路径传过来
  8     chdir("/home/wdl");
  9     while(1)
 10     {
 11         printf("我是一个进程我的id是%d\n",getpid());
 12         sleep(1);
 13     }
 14                                                                                                                                                                  
 15     return 0;
 16 } 

在这里插入图片描述
修改一下自己的代码。

				//如果是cd命令不需要创建子进程,让shell自己执行对应的命令本质就是执行系统接口
        		//像这种不需要让我们的子进程来执行而是让shell自己执行的命令 --- 内建/内置命令
   42          if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)                                                                                                
   43          {                                                                                                                                                   
   44              if(myargv[1] != NULL)
   45              {
   46                  chdir(myargv[1]);
   47                  continue;                                                                                                                                     
   48              }                                                                                                                                               
   49          }   

在这里插入图片描述

4.3进程退出码

echo $?  //记录最近一个程序的进程退出码

在这里插入图片描述
具体实现

	//两个全局变量记录子进程退出信号退出码
   12 int lastCode=0;
   13 int lastsig=0;
				//特殊处理
   57          if(myargv[0] != NULL && strcmp(myargv[0],"echo") == 0)                                                                                            
   58          {                                                                                                                                                 
   59             if(strcmp(myargv[1],"$?") == 0)                                                                                                                
   60             {                                                                                                                                              
   61                 printf("%d,%d\n",lastCode,lastsig);
   62             }
   63             else
   64             {
   65                 printf("%s\n",myargv[1]);
   66             }
   67             continue;                                                                                                                                          
   68          }    
		       
   78          //创建子进程
   79          pid_t id = fork();
   80          assert(id != -1);
   81          if(id == 0)
   82          {
   83              //子进程
   84              execvp(myargv[0],myargv);
   85              exit(1);
   86          }
   87          //父进程
   88          int status=0;
   89          pid_t ret= waitpid(id,NULL,0);
   90          assert(ret>0);
   				//记录退出信号退出码
   91          lastCode=(status>>8)&0xFF;
   92          lastsig=status&0x7F;
   93     return 0;

在这里插入图片描述

5.myshell完整代码

#include<stdio.h>
    2 #include<stdlib.h>
    3 #include<unistd.h>
    4 #include<assert.h>
    5 #include<sys/types.h>
    6 #include<sys/wait.h>
    7 #include<string.h>
    8 
    9 #define NUM 1024
   10 #define OPT_NUM 64
   11 
   12 int lastCode=0;
   13 int lastsig=0;
   14 
   15 char lineCommand[NUM];
   16 char* myargv[OPT_NUM];//指针数组存放分割的一个个字符串
   17 
   18 int main()
   19 {
   20     while(1)
   21     {
   22          //这里也可以使用环境变量来获取这些内容
   23          printf("用户名@主机名 当前路径# "); 
   24          fflush(stdout);
   25          //"ls -a -l"-----> "ls" "-a" "-l"
   26          //这里减1是为了极端情况下放\0
W> 27          char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
   28          assert(s);
   29          //清除最后一个\n  abc\n
   30          lineCommand[strlen(lineCommand)-1]=0;
   31          //测试一下
   32          //printf("test:%s\n",lineCommand);
   33          //这里以空格为分隔符                                                                                                                                  
   34          myargv[0]=strtok(lineCommand," ");
   35          //分割的是同一个字符串下一次第一个参数就置为NULL就可以了
   36          // myargv[1]=strtok(NULL,"");
   37          //这里需要实现循环注意strtok到字符串结束会返回NULL,myargv[end]=NULL
   38          int i=1;
   39			//这里对ls特殊处理
   40          if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
   41          {
   42              myargv[i++]=(char*)"--color=auto";
   43          }
   44         
W> 45          while(myargv[i++]=strtok(NULL," "));
   46         //如果是cd命令不需要创建子进程,让shell自己执行对应的命令本质就是执行系统接口
   47         //像这种不需要让我们的子进程来执行而是让shell自己执行的命令 --- 内建/内置命令
   48          if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)                                                                                                  
   49          {
   50              if(myargv[1] != NULL)
   51              {
   52                  chdir(myargv[1]);
   53                  continue;
   54              }
   55          }
   56 
   57          if(myargv[0] != NULL && strcmp(myargv[0],"echo") == 0)
   58          {
   59             if(strcmp(myargv[1],"$?") == 0)
   60             {
   61                 printf("%d,%d\n",lastCode,lastsig);
   62             }
   63             else
   64             {
   65                 printf("%s\n",myargv[1]);
   66             }
   67             continue;
   68          }
   69          
   70 //测试分割是否成功
   71 #ifdef DEBUG
   72          for(int i=0;myargv[i];++i)
   73          {
   74              printf("myargv[%d]:%s\n",i,myargv[i]);
   75          }
   76 #endif
   77 
   78          //创建子进程
   79          pid_t id = fork();
   80          assert(id != -1);
   81          if(id == 0)
   82          {
   83              //子进程
   84              execvp(myargv[0],myargv);
   85              exit(1);
   86          }
   87          //父进程
   88          int status=0;
W> 89          pid_t ret= waitpid(id,NULL,0);
   90          assert(ret>0);
   91          lastCode=(status>>8)&0xFF;
   92          lastsig=status&0x7F;
   93          return 0;
   94     }
   95 }

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: shelllinux