【lesson17】minishell(shell的模拟实现)

文章目录

  • 模拟实现shell的思路
  • 具体实现
    • 一直循环(一)
    • 显示提示行符(二)
    • 获取用户输入的字符串(三)
    • 对字符串进行解析(四)
    • 创建子进程执行指令(5)
  • 细节问题解决
    • 问题一
    • 问题二
    • 问题三
    • 问题四
  • 代码
    • myshell.c
    • mytest.c

模拟实现shell的思路

1.首先shell一定是一直循环在运行的,不然我们无法一直读取指令。
Xshell的演示:
【lesson17】minishell(shell的模拟实现)_第1张图片
我们也看到Xshell也一直在等待,命令的输入,有命令输入则执行命令,无则一直循环等待。

2.其次显示提示符
【lesson17】minishell(shell的模拟实现)_第2张图片

我们会看到Xshell一直会显示一串括号内的提示符给我们

3.然后是获取用户输入的字符串
【lesson17】minishell(shell的模拟实现)_第3张图片
要执行OS的指令,首先我们要从键盘中读取出该指令,然后才能谈之后的事。

4.对字符串进行解析
我们从键盘中读取出来的指令是一串字符串,而我们要执行的指令是分开的一个一个的字符串,所以要对读取上来的字符串进行解析。

5.创建子进程执行指令。
为什么要创建子进程执行指令呢?
因为子进程执行指令的时候如果因为指令而引发崩溃等问题就不会影响到父进程,而父进程只要聚焦于读取数据,解析数据和派发任务即可。
【lesson17】minishell(shell的模拟实现)_第4张图片
我们可以看到真实的Xshell也是如此的。执行一个不存在的指令出错。但是不会影响Xshell的使用

具体实现

一直循环(一)

【lesson17】minishell(shell的模拟实现)_第5张图片
在这里插入图片描述

显示提示行符(二)

【lesson17】minishell(shell的模拟实现)_第6张图片
但是这里会有一个问题
【lesson17】minishell(shell的模拟实现)_第7张图片
我们会看到当我们运行自己的shell的时候会发现光标是在下一行的,而不是跟在提示行符后面的,我们看一下正常Xshell。
【lesson17】minishell(shell的模拟实现)_第8张图片
我们可以看到正常Xshell光标是跟在,提示行符后面的。
那么是因为什么呢?
其实很早之前在进度条代码的实现就讲过了,是==\n的问题==,具体细节这里就不过多介绍。

而我们只要把\n去掉,用系统的fflush函数就可以了。
在这里插入图片描述
在这里插入图片描述

获取用户输入的字符串(三)

1.首先我们要定义一个全局数据,用来保存获取的数据。
【lesson17】minishell(shell的模拟实现)_第9张图片
加粗样式2.其次是对cmd_line数组初始化
【lesson17】minishell(shell的模拟实现)_第10张图片
3.用fgets函数从stdin(标准输入)中读取数据。因为shell是也一直运行的,命令会一直被输入,所以fgets函数也要一直读取数据。
【lesson17】minishell(shell的模拟实现)_第11张图片

对字符串进行解析(四)

因为读取读取上来的是一组字符串,所以要对该字符串进行解析。
1.首先要创建命令数组,保存解析后的命令。
【lesson17】minishell(shell的模拟实现)_第12张图片
2.开始解析字符串
我们可以用strtok函数来解析字符串
因为我们输入命令的时候,使用空格隔开的,所以解析命令的时候,也要按照空额解析出来。
先定义空格
【lesson17】minishell(shell的模拟实现)_第13张图片
开始解析
【lesson17】minishell(shell的模拟实现)_第14张图片
strtok函数,第一次解析某个字符串要传入该字符串,接下来如果还是解析该字符串可以不用传,直接穿个NULL即可。

strtok返回值问题:
strtok解析字符串是一个一个解析的,解析完返回解析的字符子串。
演示:
【lesson17】minishell(shell的模拟实现)_第15张图片

创建子进程执行指令(5)

子进程执行指令
【lesson17】minishell(shell的模拟实现)_第16张图片
父进程等待子进程执行指令的结果。
【lesson17】minishell(shell的模拟实现)_第17张图片

上面子进程用进程替换执行指令,这里如果有问题的话,大家请移步进行替换的博客。

到这一步基本已经可以了。但是还有一些细节性的问题需要解决。

细节问题解决

问题一

【lesson17】minishell(shell的模拟实现)_第18张图片
我们在运行ls的时候会发现,系统的ls会带颜色,而我们自己代码执行的ls不会带颜色。
这是因为系统的ls是别名
在这里插入图片描述
所以我们碰到ls指令要特别处理,其实多传个颜色即可。
【lesson17】minishell(shell的模拟实现)_第19张图片
【lesson17】minishell(shell的模拟实现)_第20张图片

问题二

【lesson17】minishell(shell的模拟实现)_第21张图片
从图中我们会发现,我们自己的ll指令无法执行,因为系统中ll指令也是别名
在这里插入图片描述
而我们也特殊处理。
【lesson17】minishell(shell的模拟实现)_第22张图片
【lesson17】minishell(shell的模拟实现)_第23张图片

问题三

【lesson17】minishell(shell的模拟实现)_第24张图片
我们从图中可以看到cd不会改变路径的,但是正常的shell cd指令是要改变路径的这里为什么没有改变呢?
因为是子进程在执行命令,而父进程只分析命令等,所以子进程的路径发生了改变,但是父进程的路径并没有发生改变。

所以cd也要特殊处理。
在这里插入图片描述
【lesson17】minishell(shell的模拟实现)_第25张图片

问题四

首先我们建一个程序查找自己用我们的程序输入的环境变量。
【lesson17】minishell(shell的模拟实现)_第26张图片
【lesson17】minishell(shell的模拟实现)_第27张图片
我们会发现shell不允许我们自己的程序使用export。
所以对export指令我们也要单独处理。
【lesson17】minishell(shell的模拟实现)_第28张图片
但是这里有个非常隐蔽的问题。
【lesson17】minishell(shell的模拟实现)_第29张图片
我们会发现我们处理的第一步,就是给要输入的环境变量重新放一个地方。

因为我们自己的程序,在export结束后,就会清空g_argv数组的数据,而export不是直接把数据放进环境变量中的,是将指向该数据的指针放进环境变量中。所以当我们要查找该环境变量的时候就会发现什么都没有。
演示:
【lesson17】minishell(shell的模拟实现)_第30张图片

所以我们要创建一个数组将其保存起来。

但是这里也没有彻底解决这个问题,因为当再次export的时候,旧的export数据就有被覆盖,从而找不到。
演示:
【lesson17】minishell(shell的模拟实现)_第31张图片

代码

myshell.c

#include 
#include 
#include 
#include 
#include 
#include 

#define NUM 1024
#define SIZE 32
#define SEP " "

char cmd_line[NUM];//array for saving command line
char* g_argv[SIZE];//the array are used to store paresed commands
char g_myval[64];
int main()
{
  //1.命令行解释器,一定是一个常驻内存的进程,不退出
  while(1)
  {
    //2.显示提示行
    printf("[xiaolin@localhost myshell]#");
    fflush(stdout);
    memset(cmd_line,'\0',sizeof cmd_line);
    //3.获取用户输入的字符串
    if(fgets(cmd_line,sizeof cmd_line,stdin) == NULL)
    {
      //if cmd_line empty contiue get command
      continue;
    }                                                                                                                                                                   
    cmd_line[strlen(cmd_line)-1] = '\0';
    //4.对字符串进行解析
    int index = 0;
    g_argv[index++] = strtok(cmd_line,SEP);//Firest parse cmd_line 
    while(1)//Second parse cmd_line don't pass cmdline;
    {
      g_argv[index] = strtok(NULL,SEP);
      if(g_argv[index] == NULL) break;
      index++;
    }
                                                                                                                                                                        
    if(strcmp(g_argv[0],"ls") == 0)
    {
      g_argv[index++] = (char*)"--color=auto";
      g_argv[index] = NULL;

    } 

    if(strcmp(g_argv[0],"ll") == 0)
    {
      g_argv[0] = (char*)"ls";
      g_argv[1] = (char*)"-l";
      g_argv[2] = (char*)"--color=auto";
      g_argv[3] = NULL;
    }
    
    // processing of built-in commands
    if(strcmp(g_argv[0],"cd") == 0)
    {
      //chdir() changes the current working directory of the calling process to the directory specified in path.
      if(g_argv[1] != NULL) chdir(g_argv[1]);
      continue;
    }
    if(strcmp(g_argv[0],"export") == 0 && g_argv[1] != NULL)
    {
      //There is a very hidden issue here
      //Ptuenv passes an environment variable as a pointer to it 
      //And g_ Argv [1] will be cleared on the next command_line read
      //In this way, the environment variable pointer points to a place with empty data, and this pointer is also a null pointer
      
      
      //int res = putenv(g_argv[1]);
      //if(res == 0) printf("export success\n");                                                                                                                        
      //else printf("export fail\n");
      
      //solve the problem
      strcpy(g_myval,g_argv[1]);
      int res = putenv(g_myval);
      if(res == 0) printf("export success\n");
      else printf("expor fail\n");
      continue;
    }
    /*//test if the g_argv array id correct
    for(index = 0; g_argv[index]; index++)
    {
      printf("g_argv[%d]:%s\n",index,g_argv[index]);
    }*/

    //5.create subprocess execute command
    pid_t id = fork();
    if(id == 0)
    {
      //subprocess
      //printf("parent process create subprocess success\n");
      printf("subprocess starts running\n");
      execvp(g_argv[0],g_argv);
      printf("subprocess replace fail\n");
      
    }
    else if(id > 0)
    {
      //parent process
      int status = 0;                                                                                                                                                   
      pid_t res = waitpid(-1,&status,0);//blocking waiting
      if(res == -1)
      {
        printf("parent process wait subprocess fail\n");
      }
      else if(res > 0)
      {
        printf("parent process wait subprocess success exit_code:%d\n",WEXITSTATUS(status));
      }
      else 
      {
        printf("unkown error\n");
      }
    }
    else 
    {
      //fail
      printf("parent procrss create subprocess fail\n");
    }
  }
  return 0;
}

mytest.c

#include     
#include     
    
int main()    
{    
  const char* s = "MYVAL";                                                                                                                                              
  char* res = getenv(s);    
  printf("%s=%s\n",s,res);    
  return 0;    
}

你可能感兴趣的:(linux,服务器,数据库,c语言)