【linux】实现shell

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

前面学过了进程创建/终止/等待/替换。现在根据所学的内容实现一个简易版的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----> 是当前进程的工作目录

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

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

子进程也有自己的工作目录,默认和父进程一样。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.重定向功能

在这里插入图片描述

//增加一个分割指令和文件名的函数                                                                                                                               
commandstrtok(lineCommand);

分割的时候,我们需要知道重定向是什么类型,文件是什么名字,因此增加两个全局变量来记录。

  //重定向类型    
  //第一个为初始重定向     
  #define DEFAULT_REDIR 0    
  #define INPUT_REDIR 1  
  #define OUTPUT_REDIR 2    
  #define ERROR_EDIRR 3    
      
  //重定向类型+文件名    
  int redirType=DEFAULT_REDIR;    
  char* redirFile=NULL;    

分割函数

  //这里找文件名没有写成函数,而写个宏
 #define trimSpace(start) do{
      
      \
      while(isspace(*start)) ++start;\
  }while(0)
  
  void commandstrtok(char* cmd)
  {
    
    
      assert(cmd);
      char* start=cmd;
      char* end=cmd+strlen(cmd);

      while(start < end)
      {
    
    
          if(*start == '<')
          {
    
    
              *start=0;
              ++start;
              //这里可能 ls -a -l >      log.txt,
              trimSpace(start);
              redirType=INPUT_REDIR;
              redirFile=start;
              break;
          }
          else if(*start == '>')                                                                                                                                 
          {
    
                                                                                                                                                          
              *start=0;                                                                                                                                          
              ++start;                                                                                                                                           
              if(*start == '>')                                                                                                                                  
              {
    
                                                                                                                                                      
                  redirType=APPEND_REDIR;                                                                                                                        
                  ++start;                                                                                                                                       
              }                                                                                                                                                  
              else                                                                                                                                               
              {
    
                                                                                                                                                      
                  redirType=OUTPUT_REDIR;                                                                                                                        
              }                                                                                                                                                  
              trimSpace(start);                                                                                                                                  
              redirFile=start;                                                                                                                                       
              break;                                                                                                                                             
          }                                                                                                                                                      
          else
          {
    
                                                                                                                                                          
              ++start;
          }
      }

因为命令是子进程执行的,真正重定向的功能一定是由子进程来完成的,
但是如何重定向是父进程要告知给子进程的。

//创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
    
                 
   switch(redirType)
   {
    
    
    	case INPUT_REDIR:
         {
    
    
             int fd=open("log.txt",O_RDONLY);
             if(fd <0)
              {
    
    
                  perror("open");
                  return 1;
               }
               //重定向文件已经打开了
                dup2(fd,0);
           }
           	 break;
           case OUTPUT_REDIR:
           case APPEND_REDIR:
           {
    
    
                umask(0);
                int flags=O_WRONLY|O_CREAT;
                if(redirType == OUTPUT_REDIR) flags|=O_CREAT;
                else flags|=O_APPEND;                                                                                                                     
                int fd=open("log.txt",flags,0666);
                if(fd < 0)
                {
    
    
                 	perror("open");
                    return 1;
                 }
                  dup2(fd,1);     
             }
               break;
             default:
             printf("bug?\n");
               break;
     }
      //程序替换
      execvp(myargv[0],myargv);
      exit(1);
}

在这里插入图片描述

6.myshell完整代码

    1 #include<stdio.h>
    2 #include<stdlib.h>
    3 #include<unistd.h>
    4 #include<assert.h>
    5 #include<sys/types.h>
    6 #include<sys/stat.h>
    7 #include<fcntl.h>
    8 #include<sys/wait.h>
    9 #include<string.h>
   10 #include<ctype.h>
   11 
   12 #define NUM 1024
   13 #define OPT_NUM 64
   14 
   15 //重定向类型
   16 //第一个为初始重定向 
   17 #define DEFAULT_REDIR 0
   18 #define INPUT_REDIR 1
   19 #define OUTPUT_REDIR 2
   20 #define APPEND_REDIR 3
   21 
   22 //重定向类型+文件名
   23 int redirType=DEFAULT_REDIR;
   24 char* redirFile=NULL;
   25 
   26 int lastCode=0;
   27 int lastsig=0;
   28 
   29 char lineCommand[NUM];
   30 char* myargv[OPT_NUM];//指针数组,存放分割的一个个字符串
   31 
   32 //这里找文件名没有写成函数,而写个宏
   33 #define trimSpace(start) do{
    
    \                                                                                                                                  
   34     while(isspace(*start)) ++start;\
   35 }while(0)
   36 
   37 void commandstrtok(char* cmd)
   38 {
    
    
   39	  assert(cmd);
   40     char* start=cmd;
   41     char* end=cmd+strlen(cmd);
   42     while(start < end)                                                                                                                                         
   43     {
    
    
   44         if(*start == '<')
   45         {
    
    
   46             *start=0;
   47             ++start;
   48             //这里可能 ls -a -l >      log.txt,
   49             trimSpace(start);
   50             redirType=INPUT_REDIR;
   51             redirFile=start;
   52             break;
   53         }
   54         else if(*start == '>')
   55         {
    
    
   56             *start=0;
   57             ++start;
   58             if(*start == '>')
   59             {
    
    
   60                 redirType=APPEND_REDIR;
   61                 ++start;
   62             }
   63             else
   64             {
    
    
   65                 redirType=OUTPUT_REDIR;
   66             }
   67             trimSpace(start);
   68             redirFile=start;
   69             break;
   70         }
   71         else
   72         {
    
    
   73             ++start;
   74         }
   75     }
   76 
   77 }
   78 
   79 int main()
   80 {
    
    
   81    while(1)
   82    {
    
    
   83        //防止输入ls -a -l > log.txt,在输入ls -a -l还大概在重定向来执行
   84          redirType=DEFAULT_REDIR;
   85          redirFile=NULL;
   86          //这里也可以使用环境变量来获取这些内容
   87          printf("用户名@主机名 当前路径# "); 
   88          fflush(stdout);
   89          //"ls -a -l"-----> "ls" "-a" "-l"
   90          //这里减1,是为了极端情况下放\0
W> 91          char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
   92          assert(s);
   93 
   94         //增加重定向功能
   95         //"ls -a -l > log.txt"      "ls -a -l"  "log.txt"
   96         //"ls -a -l >> log.txt"     "ls -a -l"  "log.txt"
   97         //"cat < log.txt"           "cat"       "log.txt"
   98         //分割指令和文件名的函数
   99         commandstrtok(lineCommand);
  100 
  101          //清除最后一个\n  abc\n
  lineCommand[strlen(lineCommand)-1]=0;                                                                                                                 
  103          //测试一下
  104          //printf("test:%s\n",lineCommand);
  105          //这里以空格为分隔符
  106          myargv[0]=strtok(lineCommand," ");
  107          //分割的是同一个字符串,下一次第一个参数就置为NULL就可以了
  108          // myargv[1]=strtok(NULL,"");
  109          //这里需要实现循环,注意strtok到字符串结束,会返回NULL,myargv[end]=NULL
  110          int i=1;
  111          //这里对ls,特殊处理
  112          if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
  113          {
    
    
  114              myargv[i++]=(char*)"--color=auto";
  115          }
  116         
W>117          while(myargv[i++]=strtok(NULL," "));
  118         //如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
  119         //像这种不需要让我们的子进程来执行,而是让shell自己执行的命令 --- 内建/内置命令
  120          if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
  121          {
    
    
  122              if(myargv[1] != NULL)
  123              {
    
    
  124                  chdir(myargv[1]);
  125                  continue;
  126              }
  127          }
  128 
  129          if(myargv[0] != NULL && strcmp(myargv[0],"echo") == 0)
  130          {
    
    
  131             if(strcmp(myargv[1],"$?") == 0)
  132             {
    
    
  133                 printf("%d,%d\n",lastCode,lastsig);
  134             }
  135             else                                                                                                                                               
  136             {
    
    
  137                 printf("%s\n",myargv[1]);
  138             }
  139             continue;
  140          }
  141          
  142 //测试分割是否成功
  143 #ifdef DEBUG
  144          for(int i=0;myargv[i];++i)
  145          {
    
    
  146              printf("myargv[%d]:%s\n",i,myargv[i]);
  147          }
  148 #endif
  149 
  150          //创建子进程
  151          pid_t id = fork();
  152          assert(id != -1);
  153          if(id == 0)
  154          {
    
    
  155              
  156              switch(redirType)
  157              {
    
    
  158                  case DEFAULT_REDIR:
  159                      //什么都不做
  160                      break;
  161                  case INPUT_REDIR:
  162                      {
    
    
  163                          int fd=open("log.txt",O_RDONLY);
  164                          if(fd <0)
  165                          {
    
    
  166                              perror("open");
  167                              return 1;
  168                          }                                                                                                                                     
  169                          //重定向文件已经打开了
  170                          dup2(fd,0);
  171                      }
  172                      break;
  173                  case OUTPUT_REDIR:
  174                  case APPEND_REDIR:
  175                      {
    
    
  176                          umask(0);
  177                          int flags=O_WRONLY|O_CREAT;
  178                          if(redirType == OUTPUT_REDIR) flags|=O_TRUNC;
  179                          else flags|=O_APPEND;
  180                          int fd=open("log.txt",flags,0666);
  181                          if(fd < 0)
  182                          {
    
    
  183                              perror("open");
  184                              return 1;
  185                          }
  186                          dup2(fd,1);
  187                      }
  188                      break;
  189                  default:
  190                     printf("bug?\n");
  191                     break;
  192 
  193              }
  194              //子进程
  195              execvp(myargv[0],myargv);
  196              exit(1);
  197          }
  198          //父进程
  199          int status=0;
W>200          pid_t ret= waitpid(id,NULL,0);
  201          assert(ret>0);
  202          lastCode=(status>>8)&0xFF;
  203          lastsig=status&0x7F;
  204    }
  205          return 0;
  206 }

猜你喜欢

转载自blog.csdn.net/fight_p/article/details/133248621