Linux下MiniShell的简单实现

1. 思路

在学习了linux进程概念和进程控制以后,简单的来实现一个minishell。

  1. 启动一个父进程,用以从标准输入里面读取用户输入的命令行内容
  2. 由父进程解析用户输入的内容中,哪些是命令,哪些是命令行参数,存放进argv数组中
  3. 父进程fork一个子进程,让子进程进行程序替换(execvp),执行用户输入的命令,对命令参数为NULL,或替换失败的子进程进行退出。
  4. 父进程阻塞的等待子进程退出(wait),待子进程退出后,再去循环获取用户输入

思路图示:
在这里插入图片描述

2. 实现代码
2.1 头文件及宏定义部分
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>

#define MAX_CMD 1024
char command[MAX_CMD];
2.2 获取用户输入部分
2.2.1 用fgets()获取
int get_command()
{
  memset(command,0,MAX_CMD);//初始化数组
  printf("[minishell@localhost ~]");
  fflush(stdout); //刷新缓冲区

  if(fgets(command,sizeof(command)-1,stdin) == NULL) //stdin 标准输入
  {
    return -1; //没有读取到内容则返回-1
  }
  return 0;
}
2.2.2 用scanf()获取
int get_command()  // 读取用户输入
{
  memset(command,0,MAX_CMD);
   printf("[minishell@localhost ~]");
  fflush(stdout);
  /*
   * %[^\n] 表示读入一行字符串,遇到\n结束 
   * %*c 表示清理输入缓冲区第一个字符,也就是上次遗留下的\n
   */ 
  if(scanf("%[^\n]%*c",command) == 0) //scanf读取输入,如果没有读取到输入就返回-1
  {
    getchar();
    return -1;
  }
  return 0;
}

2.3 解析命令行部分
char **do_parse(char *buff) // 处理输入的字符数组
{
  int argc = 0;
  static char *argv[32]; // 创建一个指针数组
  char *ptr = buff;// 定义ptr指向buff字符数组

  while(*ptr != '\0')
  {
    if(!isspace(*ptr))
    {
      argv[argc++] = ptr;//如果*ptr不为空格,则将ptr这个指针保存到argv数组中
      while(!isspace(*ptr) && *ptr != '\0')
      {
        ptr++;   // 直到遇到空格停下
      }
    }else{
      while(isspace(*ptr)) // 将字符数组中空格字符赋值为'\0'
      {
        *ptr = '\0';
        ptr++;
      }
    }
  }

  argv[argc] = NULL; // 因为最后要传给exec函数的可变参数数组argv要以NULL结尾,所以给所存数据的后一个位置赋值NULL
  return argv; //返回该指针数组
}

上面的while循环也可以这样写,作用一样:

  while(*ptr != '\0')
  {
    while(!isspace(*ptr) && *ptr != '\0')
    {
      argv[argc++] = ptr;
      while(!isspace(*ptr) && *ptr != '\0')
      {
        ptr++;
      }
      *ptr = '\0';
    }    
    ptr++;
  }
2.4 创建子进程并进行替换部分
int do_myexec(char *buff)
{
  char **argv = do_parse(buff); // 定义一个二级指针指向存放命令及参数的argv
  pid_t pid = fork(); // 创建一个子进程 
  if(pid < 0 )
  {
    perror("fork");
  }else if(pid == 0){
    //child 进行程序替换
    if(argv[0] == NULL) // 如果传入的参数为空,子进程退出
    {
      exit(-1);
    }
   int ret =  execvp(argv[0],argv); // 数组传参,带p表示会从环境变量找,传入程序名称即可,argv的第一个变量即为命令名称
   if(ret < 0) //返回值小于0,这说明替换失败了
   {
     printf("bash: %s: 未找到命令...\n",argv[0]);
     exit(0); // 让替换失败的子进程退出
   }
  }else{
    // parent   
    waitpid(pid,NULL,0);// 父进程阻塞等待子进程退出
    //wait(NULL); //用这个与waitpid效果一致
  }
  return 0;
}
2.5 main函数部分
int main()
{
  while(1)
  {
    if(get_command() < 0)
    {
      continue;  // 如果没有读取到输入,则跳过继续读取
    }
    do_myexec(command); // 进行替换
  }
  return 0;
}
3. 运行结果

如果父进程不阻塞等待子进程:
在这里插入图片描述
父进程阻塞等待子进程:
在这里插入图片描述
传入的可执行程序所在路径不再环境变量中时:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值