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

模拟实现shell的思路

1.首先shell一定是一直循环在运行的,不然我们无法一直读取指令。
Xshell的演示:
在这里插入图片描述
我们也看到Xshell也一直在等待,命令的输入,有命令输入则执行命令,无则一直循环等待。

2.其次显示提示符
在这里插入图片描述

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

3.然后是获取用户输入的字符串
在这里插入图片描述
要执行OS的指令,首先我们要从键盘中读取出该指令,然后才能谈之后的事。

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

5.创建子进程执行指令。
为什么要创建子进程执行指令呢?
因为子进程执行指令的时候如果因为指令而引发崩溃等问题就不会影响到父进程,而父进程只要聚焦于读取数据,解析数据和派发任务即可。
在这里插入图片描述
我们可以看到真实的Xshell也是如此的。执行一个不存在的指令出错。但是不会影响Xshell的使用

具体实现

一直循环(一)

在这里插入图片描述
在这里插入图片描述

显示提示行符(二)

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

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

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

1.首先我们要定义一个全局数据,用来保存获取的数据。
在这里插入图片描述
加粗样式2.其次是对cmd_line数组初始化
在这里插入图片描述
3.用fgets函数从stdin(标准输入)中读取数据。因为shell是也一直运行的,命令会一直被输入,所以fgets函数也要一直读取数据。
在这里插入图片描述

对字符串进行解析(四)

因为读取读取上来的是一组字符串,所以要对该字符串进行解析。
1.首先要创建命令数组,保存解析后的命令。
在这里插入图片描述
2.开始解析字符串
我们可以用strtok函数来解析字符串
因为我们输入命令的时候,使用空格隔开的,所以解析命令的时候,也要按照空额解析出来。
先定义空格
在这里插入图片描述
开始解析
在这里插入图片描述
strtok函数,第一次解析某个字符串要传入该字符串,接下来如果还是解析该字符串可以不用传,直接穿个NULL即可。

strtok返回值问题:
strtok解析字符串是一个一个解析的,解析完返回解析的字符子串。
演示:
在这里插入图片描述

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

子进程执行指令
在这里插入图片描述
父进程等待子进程执行指令的结果。
在这里插入图片描述

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

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

细节问题解决

问题一

在这里插入图片描述
我们在运行ls的时候会发现,系统的ls会带颜色,而我们自己代码执行的ls不会带颜色。
这是因为系统的ls是别名
在这里插入图片描述
所以我们碰到ls指令要特别处理,其实多传个颜色即可。
在这里插入图片描述
在这里插入图片描述

问题二

在这里插入图片描述
从图中我们会发现,我们自己的ll指令无法执行,因为系统中ll指令也是别名
在这里插入图片描述
而我们也特殊处理。
在这里插入图片描述
在这里插入图片描述

问题三

在这里插入图片描述
我们从图中可以看到cd不会改变路径的,但是正常的shell cd指令是要改变路径的这里为什么没有改变呢?
因为是子进程在执行命令,而父进程只分析命令等,所以子进程的路径发生了改变,但是父进程的路径并没有发生改变。

所以cd也要特殊处理。
在这里插入图片描述
在这里插入图片描述

问题四

首先我们建一个程序查找自己用我们的程序输入的环境变量。
在这里插入图片描述
在这里插入图片描述
我们会发现shell不允许我们自己的程序使用export。
所以对export指令我们也要单独处理。
在这里插入图片描述
但是这里有个非常隐蔽的问题。
在这里插入图片描述
我们会发现我们处理的第一步,就是给要输入的环境变量重新放一个地方。

因为我们自己的程序,在export结束后,就会清空g_argv数组的数据,而export不是直接把数据放进环境变量中的,是将指向该数据的指针放进环境变量中。所以当我们要查找该环境变量的时候就会发现什么都没有。
演示:
在这里插入图片描述

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

但是这里也没有彻底解决这个问题,因为当再次export的时候,旧的export数据就有被覆盖,从而找不到。
演示:
在这里插入图片描述

代码

myshell.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

#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 <stdio.h>    
#include <stdlib.h>    
    
int main()    
{    
  const char* s = "MYVAL";                                                                                                                                              
  char* res = getenv(s);    
  printf("%s=%s\n",s,res);    
  return 0;    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值