基本shell功能实现(exec系列程序替换函数练习)

发布时间:2023年12月21日

功能描述

实现一个类似于shell的命令行解释器。通过让子进程执行命令,父进程等待等待并解析命令,从而可以执行类似于“ls”,“ls -a -l -i”,'pwd"等linux指令。

思路介绍

1.实现常驻进程功能

在这里将要实现一个死循环,并且打印出提示信息。

while(1)
{
	//命令行解释器一定是一个常驻内存的进程,不退出
	//打印出提示信息[]
   23     printf("[xty@localhost myshell]#] ");
   24     fflush(stdout);
}

运行如图:
在这里插入图片描述

2.实现命令读取功能

使用fgets函数读取输入的内容,注意要注意把回车给删除(因为fgets会把回车也读取进来)

	 #define NUM 1024
	 //保存完整的命令字符串
	 char cmd_line[NUM];

     //2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]
     memset(cmd_line, '\0', sizeof(cmd_line));
     if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
     {
       continue;
     }
     cmd_line[strlen(cmd_line)-1] = '\0';
     //发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个
     //printf("echo:%s \n", cmd_line);

3. 实现命令解析功能

需要将我们输入的命令行字符串,变成shell能理解的语言。
将命令行选项使用strtok分开,把 "ls -a -l"变成 “ls”, “-a”, “-l”, “NULL”
为后面的execvp作准备。

#define SIZE 32
//保存打散之后的命令行字符串
char *g_argv[SIZE];

//3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"
g_argv[0] = strtok(cmd_line, " ");
int index = 1;
while(g_argv[index++] = strtok(NULL, " "));


//检查一下g_argv对不对
for(index = 0; g_argv[index];index++)
{
  //虽然存入的是地址,但是%s,会将它看成字符串打印出来
  printf("g_argv[%d] = %s\n", index, g_argv[index]);
}

结果如下:
在这里插入图片描述

4.实现子进程执行命令功能

子进程执行命令,父进程等待子进程返回。

     //4.让子进程执行命令,执行完后给父进程返回值
     pid_t id = fork();
     if(id==0)
     {
       //子进程,执行命令 
       printf("子进程开始执行任务\n");                                                                     
       execvp(g_argv[0], g_argv);
       exit(1);
     }
    
     //father
     int status = 0;
     pid_t ret = waitpid(id, &status, 0);
     if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));

5.完善功能

让"ls"有颜色,并且让shell认识"ls"命令。
完整代码如下:

1 #include<stdio.h>
2 #include<unistd.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 SIZE 32
10 //保存完整的命令字符串
11 char cmd_line[NUM];
12 //保存打散之后的命令行字符串
13 char *g_argv[SIZE];
14 
15 
16 //shell运行原理:通过让子进程执行命令,父进程等待&&解析命令
17 int main()
18 {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
19   //0.命令行解释器一定是一个常驻内存的进程,不退出
20   while(1)
21   {
22     //1.打印出提示信息[]
23     printf("[xty@localhost myshell]#] ");
24     fflush(stdout);
25 
26     //2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]
27     memset(cmd_line, '\0', sizeof(cmd_line));
28     if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
29     {
30       continue;
31     }
32     if(cmd_line[0] == '\n')
33     {
34       continue;
35     }
36     cmd_line[strlen(cmd_line)-1] = '\0';
37     //发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个
38     //printf("echo:%s \n", cmd_line);
39 
40     //3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"
41     g_argv[0] = strtok(cmd_line, " ");
42     int index = 1;
43 
44     if(strcmp(g_argv[0], "ls")==0)
45     {
46       g_argv[index++]="--color=auto";
47     }
48     //还可以让编译器支"ll"
49     if(strcmp(g_argv[0],"ll")==0)
50     {
51       g_argv[0] = "ls";
52       g_argv[index++] = "-l";
53       g_argv[index++] = "--color=auto";
54     }
55 
56     //让ls命令有颜色
57     while(g_argv[index++] = strtok(NULL, " "));
58 
59     //检查一下g_argv对不对
60     for(index = 0; g_argv[index];index++)
61     {
62       //虽然存入的是地址,但是%s,会将它看成字符串打印出来
63       printf("g_argv[%d] = %s\n", index, g_argv[index]);
64     }
65 
66     //4.让子进程执行命令,执行完后给父进程返回值
67     pid_t id = fork();
68     if(id==0)
69     {
70       //子进程,执行命令 
71       printf("子进程开始执行任务\n");
72       execvp(g_argv[0], g_argv);
73       exit(1);
74     }
75 
76     //father
77     int status = 0;
78     pid_t ret = waitpid(id, &status, 0);
79     if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));
80   }
81   return 0;
82 }

这样我们一个简易的shell就完成了。

补充内容

shell执行的命令,通常有两种:

  1. 第三方提供的对应在磁盘中有具体二进制文件的可执行程序(由子进程执行)
  2. shell内部,自己实现的方法,由(父进程)来执行,有些命令会影响到shell本身,比如:“cd”,"export"命令等。

让父进程运行内置命令

当我们执行cd命令时,发现程序并没有改变目录。如下图:
原因是:cd命令被子进程执行了,子进程的当前目录被修改了,但是子进程立马就退出了。但是并没有影响到父进程的当前目录,所以第二次再次运行的时候,子进程还是在父进程的目录创建的,因此没有改变。
在这里插入图片描述

使用chdir函数改变父进程的工作目录。
代码如下:

   66     //让父进程执行cd 命令
   67     if(strcmp(g_argv[0], "cd")==0)
   68     {
   69       if(g_argv[1]!=NULL) chdir(g_argv[1]); // cd .., cd path
   70       continue;                                                                                                                                                   
   71     }    

结果如下图:
在这里插入图片描述

实现子进程能够获得父进程的环境变量功能(export命令)

因export和其他的命令行功能不一样,所以需要再判断一下该功能。并且需要创建新数组保存一下环境变量,因为cmd_line再第二次读取时会清空,会导致getenv时得不到被清空的环境变量(因为存的是指针地址,清空后变成了野指针),因此需要创建数组存储一下!

代码如下:


//myshell.c
 //写一个环境变量的bufer,用来存储临时的环境变量,防止cmd_line被清空
 char my_num[64];
 
 // export MYNUM=111222333
 if(strcmp(g_argv[0], "export")==0 && g_argv[1]!=NULL)
 {
   strcpy(my_num, g_argv[1]);
   int ret = putenv(my_num);
   if (ret == 0) printf("%s export sucess\n", my_num);  //查看一下导入的是什么
   continue;
 }
 

 //env_test.c
  1 #include<stdio.h>
  2 #include<stdlib.h>                                                                                                                                                  
  3 
  4 int main()
  5 {
  6   printf("我是测试环境变量是否成功导入进程\n");
  7   printf("MYNUM= %s \n",getenv("MYNUM"));
  8 
  9 }



在这里插入图片描述

shell实现重定向功能

先检查是否有重定向的功能,然后再执行命令。

//检查命令模块
    //定义标志位的含义                                                                                                                                                                                                                         
   22 #define INPUT_REDIR 1
   23 #define OUTPUT_REDIR 2
   24 #define APPEND_REDIR 3
   25 #define NONE_REDIR 0
   26 int redir_status = NONE_REDIR;
   27 
   28 char *CheckRedir(char *start)
   29 {
   30   assert(start);
   31   char *end = start + strlen(start) - 1;// ls -a -l
   32 
   33   //从后往前找
   34   while(end >= start)
   35   {
   36     if(*end == '>')
   37     {
   38       if(*(end - 1) == '>')
   39       {
   40         redir_status = APPEND_REDIR;
   41         *(end - 1) = '\0';
   42         end++;  //重定向后字符串的首地址
   43         break;
   44       }
   45       redir_status = OUTPUT_REDIR;
   46       *end = '\0';
   47       end++;   // ls -a>myfile.txt   ->ls -a\0myflie.txt
   48       break;
   49     }
   50     else if(*end == '<')
   51     {
   52       redir_status = INPUT_REDIR;
   53       *end = '\0';
   54       end++;
   55       break;
   56     }
   57     else{
   58       end--;
   59     }
   60   }
   61   if(end>=start)
   62   {
   63     return end; //要打开的文件名
   64   }
   65   else{
   66     return NULL;  //没有重定向功能
   67   }
   68 } 





		//main函数内部,子进程执行命令前,多一个重定向打开文件的逻辑:

  142     //4.让子进程执行命令,执行完后给父进程返回值
  143     pid_t id = fork();
  144     if(id==0)
  145     {
  146       //子进程,执行命令 
  147       printf("子进程开始执行任务\n");
  148       if(sep!=NULL)
  149       {
  150         int fd = -1;
  151         //有重定向
  152         switch(redir_status)
  153         {
  154           case INPUT_REDIR:
  155             fd = open(sep, O_RDONLY);
  156             dup2(fd, 0);
  157             break;
  158           case OUTPUT_REDIR:
  159             fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
  160             dup2(fd, 1);
  161             break;
  162           case APPEND_REDIR:
  163             fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
  164             dup2(fd, 1);
  165             break;
  166           default:
  167             printf("erro????????\n");
  168             break;
  169         }
  170       }
  171       
  172       execvp(g_argv[0], g_argv);
  173       exit(1);
  174     }
  175    


全部代码如下:

    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 #include<string.h>
    5 #include<assert.h>
    6 #include<fcntl.h>
    7 #include<sys/wait.h>
    8 #include<sys/stat.h>
    9 #include<sys/types.h>
   10 
   11 #define NUM 1024
   12 #define SIZE 32
   13 //保存完整的命令字符串
   14 char cmd_line[NUM];
   15 //保存打散之后的命令行字符串
   16 char *g_argv[SIZE];
   17 
   18 //写一个环境变量的bufer,用来存储临时的环境变量,防止cmd_line被清空
   19 char my_num[64];
   20 
   21 //定义标志位的含义
   22 #define INPUT_REDIR 1
   23 #define OUTPUT_REDIR 2
   24 #define APPEND_REDIR 3
   25 #define NONE_REDIR 0
   26 int redir_status = NONE_REDIR;
   27 
   28 char *CheckRedir(char *start)
   29 {
   30   assert(start);
   31   char *end = start + strlen(start) - 1;// ls -a -l
   32   
   33   //从后往前找
   34   while(end >= start)
   35   {
   36     if(*end == '>')
   37     {
   38       if(*(end - 1) == '>')
   39       {
   40         redir_status = APPEND_REDIR; 
   41         *(end - 1) = '\0';
   42         end++;  //重定向后字符串的首地址
   43         break;
   44       }
   45       redir_status = OUTPUT_REDIR;
   46       *end = '\0';
   47       end++;   // ls -a>myfile.txt   ->ls -a\0myflie.txt
   48       break;
   49     }
   50     else if(*end == '<')
   51     {
   52       redir_status = INPUT_REDIR;
   53       *end = '\0';
   54       end++;
   55       break;
   56     }
   57     else{
   58       end--;
   59     }
   60   }
   61   if(end>=start)
   62   {
   63     return end; //要打开的文件名
   64   }
   65   else{
   66     return NULL;  //没有重定向功能
   67   }
   68 } 
   69 
   70 //shell运行原理:通过让子进程执行命令,父进程等待&&解析命令
   71 int main()
   72 {
   73   //0.命令行解释器一定是一个常驻内存的进程,不退出
   74   while(1)
   75   {
   76     //1.打印出提示信息[]
   77     printf("[xty@localhost myshell]#] ");
   78     fflush(stdout);
   79 
   80     //2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]
   81     memset(cmd_line, '\0', sizeof(cmd_line));
   82     if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
   83     {
   84       continue;
   85     }
   86     if(cmd_line[0] == '\n')
   87     {
   88       continue;
   89     }
   90     cmd_line[strlen(cmd_line)-1] = '\0';
   91     //发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个
   92     //printf("echo:%s \n", cmd_line);
   93     
   94 
   95     //在分析指令之前就检查有没有重定向
   96     char* sep = CheckRedir(cmd_line);
   97     
   98     //3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"
   99     g_argv[0] = strtok(cmd_line, " ");
  100     int index = 1;
  101 
  102     if(strcmp(g_argv[0], "ls")==0)
  103     {
W>104       g_argv[index++]="--color=auto";
  105     }
  106     //还可以让编译器支"ll"
  107     if(strcmp(g_argv[0],"ll")==0)
  108     {
W>109       g_argv[0] = "ls";
W>110       g_argv[index++] = "-l";
W>111       g_argv[index++] = "--color=auto";
  112     }
  113 
  114     //让ls命令有颜色
W>115     while(g_argv[index++] = strtok(NULL, " "));
  116     
  117     检查一下g_argv对不对
  118     //for(index = 0; g_argv[index];index++)
  119     //{
  120     //  //虽然存入的是地址,但是%s,会将它看成字符串打印出来
  121     //  printf("g_argv[%d] = %s\n", index, g_argv[index]);
  122     //}
  123 
  124 
  125     // export MYNUM=111222333
  126     if(strcmp(g_argv[0], "export")==0 && g_argv[1]!=NULL)
  127     {
  128       strcpy(my_num, g_argv[1]);
  129       int ret = putenv(my_num);
  130       if (ret == 0) printf("%s export sucess\n", my_num);  //查看一下导入的是什么
  131       continue;
  132     }
  133 
  134 
  135 
  136     //让父进程执行cd 命令
  137     if(strcmp(g_argv[0], "cd")==0)
  138     {
  139       if(g_argv[1]!=NULL) chdir(g_argv[1]); // cd .., cd path
  140       continue;
  141     }
  142     //4.让子进程执行命令,执行完后给父进程返回值
  143     pid_t id = fork();
  144     if(id==0)
  145     {
  146       //子进程,执行命令 
  147       printf("子进程开始执行任务\n");
  148       if(sep!=NULL)
  149       {
  150         int fd = -1;
  151         //有重定向
  152         switch(redir_status)
  153         {
  154           case INPUT_REDIR:
  155             fd = open(sep, O_RDONLY);
  156             dup2(fd, 0);
  157             break;
  158           case OUTPUT_REDIR:
  159             fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
  160             dup2(fd, 1);
  161             break;
  162           case APPEND_REDIR:
  163             fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
  164             dup2(fd, 1);
  165             break;
  166           default:
  167             printf("erro????????\n");
  168             break;
  169         }
  170       }
  171 
  172       execvp(g_argv[0], g_argv);
  173       exit(1);
  174     }
  175 
  176     //father
  177     int status = 0;
  178     pid_t ret = waitpid(id, &status, 0);
  179     if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));
  180   }
  181   return 0;
  182 }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
文章来源:https://blog.csdn.net/weixin_45153969/article/details/135048621
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。