【Linux】教你用进程替换制作一个简单的Shell解释器

这篇具有很好参考价值的文章主要介绍了【Linux】教你用进程替换制作一个简单的Shell解释器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本章的代码可以访问这里获取。

由于程序代码是一体的,本章在分开讲解各部分的实现时,代码可能有些跳跃,建议在讲解各部分实现后看一下源代码方便理解程序。


一、观察Shell的运行状态

我们想要制作一个简单的Shell解释器,需要先观察Shell是怎么运行的,根据Shell的运行状态我们再去进行模拟实现。

我们可以先考虑下面的指令与Shell的互动:

【Linux】教你用进程替换制作一个简单的Shell解释器

我们仔细进行分析可以发现,Shell执行上面的命令时,可以被理解为下面的过程。

【Linux】教你用进程替换制作一个简单的Shell解释器
当然上面的命令都是普通命令,所以Shell都是通过创建子进程的方式来执行的,对于一些内建命令(Shell自己去执行命令)我们现在还不考虑,在后面的部分我们再进行进一步的讨论内建命令应该怎么去处理。

二、简单的Shell解释器制作原理

通过观察Shell的运行状态,我们知道然后Shell读取新的一行输入,建立一个新的子进程,在这个子进程中运行程序并等待这个进程结束。

所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork
  4. 替换子进程(execvp),执行替换后的程序
  5. 父进程等待子进程退出(wait

1、获取命令行

我们在在Shell中输入的命令本质上就是输入一个字符串,因此我们想要获取命令行,可以先创建一个字符数组commandstr,然后使用C语言的fgets函数从键盘中进行读取数据到字符数组里面,这样我们就获取了一个命令行了。

注意:

  1. 这里不能使用scanf函数 ,这里的命令会包含空格会导致scanf读取不到完整的数据。
  2. fgets函数会将我们输入的命令时的最后一个的\n符也给读取到字符数组内,我们需要特殊处理将\n进行用\0进行覆盖
//这里包含的头文件是我们整个程序需要用到的所有头文件
#include<stdio.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>

//这里的N用于定义字符数组的大小
#define N 128

int main()
{
  //存储命令行的字符数组
  char commandstr[N] = "";
  
  //Shell要一直运行接受命令,所以这里必须是死循环!
  while(1)
  {
  	//模拟Shell的提示符
    printf("[hong@machine MiniShell]# ");

    //从标准输入流中读取字符串
    char* s = fgets(commandstr, sizeof(commandstr), stdin);
    assert(s); //判断fgets是否读取成功


    //处理\n   示例字符串:ls -a -l\n\0
    commandstr[strlen(commandstr) - 1] = '\0';
   }
  

2、解析命令行

虽然我们通过前一步已经拿到命令行,但是我们还不能直接使用,因为我们拿到的字符串中间可能有许多空格以及一些其他的问题,我们还需要将命令行的字符进行切割提取出我们想要的子串,这样才符合程序替换函数的要求。例如:将 ls -a -l提取成 ls ,-a , -l

对于字符串的切割,我们可以使用C语言提供的strtok函数,由于切割以后我们的字符串从一个变成了多个,因此我们需要用一个字符串指针数组argv,存储每一部分切割后的首地址,同时这个argv也可以直接传递给execvp函数进行程序替换了。

//在全局域中 定义切割符
#define SEP " "

//main函数的外部 定义一个命令行切割函数
int split(char commandstr[], char* argv[])
{
  assert(commandstr);
  assert(argv);
  
  //第一次切割
  argv[0] = strtok(commandstr, SEP);
  if(argv[0] == NULL)
  {
  	//返回 -1表示异常退出
    return -1; 
  }

  //循环切割
  int i = 1;
  while((argv[i++] = strtok(NULL, SEP)));
  return 0;
}

//main函数内部,while循环上面定义切割后的字符指针数组
char* argv[N] ={NULL};


//while循环内部

	//切割字符串  例如将"ls -a -l " 变为 "ls" "-a" "-l"
    int n = split(commandstr, argv);
    if(n == -1)
    {
      //切割失败就终止本次循环
      continue;
    }

【Linux】教你用进程替换制作一个简单的Shell解释器

3、创建子进程 进行程序替换 父进程等待

创建子进程而我们可以使用fork函数进行创建,创建完以后进程的执行流由一个变成了两个,我们在子进程中进行程序替换可以使用execvp命令,同时我们的argv[0]就是程序名,argv中存储的就是命令按照什么方式进行执行。

最后我们的父进程可以在外面进行阻塞等待,然后获取子进程的退出码和退出信息。

//main函数内部,while循环上面定义退出码变量
 int last_status = 0;
 
//while循环内部

//创建子进程,进行命令处理
    pid_t id = fork();
    assert(id >= 0);

    if(id == 0)
    {
      //child process
      execvp(argv[0], argv);  
      //如果执行到这里说明程序替换失败  
      exit(-1);
    }
	
	//父进程等待子进程
    int status;
    int pid = waitpid(id, &status, 0); 
    //等待成功就提取退出码信息
    if(pid >= 0)
    {
      last_status = WEXITSTATUS(status);
    }
  }
  return 0;

4、实际运行

我们可以执行 ls pwd ps -axj命令 看一看效果。
【Linux】教你用进程替换制作一个简单的Shell解释器

二、对简单的内建命令进行处理

我们知道内建命令是让Shell自己执行的命令,而不是让子进程执行的命令,例如cd命令就是内建命令,因为我们要改变的是Shell自己的工作目录,而不是子进程的工作目录,类似的命令还有export env echo命令。

由于上面我们写的程序执行命令时都是交给子进程去做的,所以我们上面写的程序是没有办法执行内建命令的,或者说能执行内建命令,但不是我们想要的结果或目的。

所以接下来我们要对这个简单的Shell进行改造,让它能够执行一些简单的内建命令,还有刚刚我们的ls命令没有色彩,我们也要进行一些修改。

1、给ls命令加上色彩

在真正的Shell中我们执行的ls命令其实是ls --color=autols被我们真正的Shell进行了起别名。

【Linux】教你用进程替换制作一个简单的Shell解释器
我们在运行我们自己制作的Shell时也可以加上--color=auto

//此段代码应该在切割字符串之后

//argv[0]就是我们的命令名
if(strcmp(argv[0], "ls") == 0)
    {
      int pos = 0;
      //寻找指针数组的结尾
      while(argv[pos++]);
      //在NULL位置加上 --color=auto
      argv[pos - 1] = "--color=auto";
      //将后一个位置置空
      argv[pos] = NULL;
    }

这样以后我们在我们自己制作的Shell中执行ls命令时也会由颜色了!

2、支持cd命令

对于cd命令如果让父进程进行执行,我们可以调用系统调用chdir我们只需要传递一个参数:路径字符串,当执行成功时会返回0,执行失败会返回-1,并设置错误码。

【Linux】教你用进程替换制作一个简单的Shell解释器

//此段代码应该在ls添加颜色之后

else if(strcmp(argv[0], "cd") == 0)
    {
      //argv[1]里面存放的是路径字符串
      if(argv[1] == NULL)
      {
        printf("没有正确的路径!\n");
        //设置错误码
        last_status = -1;
        continue;
      }
      	//执行系统调用改变父进程的工作目录
        chdir(argv[1]);
        continue;
    }

3、支持export命令

export命令可以将一个本地变量加入到环境变量表中,我们让我们自己制作的Shell完成expoprt命令可以用C语言提供的函数putenv函数,但是在向环境变量表加入新的环境变量时,我们要维护好我们加入到环境变量,这个环境变量不能够被轻易的覆盖,否则环境变量表在找我们的环境变量时就会找不到,所以我们还要创建一个我们自己维护的二维数组。

//在全局域中定义
// 自己维护的二维数组最多能向环境变量表几个自定义的环境变量
#define MAX 64

//main函数内部,while循环上面定义
//指向下一个要添加的环境变量的位置
 int env_index = 0;
//要维护的二维数组
 char envstr[MAX][N];

//此段代码应该在ls添加颜色之后
 else if(strcmp(argv[0], "export") == 0)
    {
    //声明putenv函数否则会编译器会有警告
      extern int putenv(char *string); 
      //argv[1]位置应该是环境变量
      if(argv[1] == NULL)
      {
        printf("没有输入变量!\n");
        last_status = -1;
        continue;
      }
      //将argv[1]位置的环境变量,拷贝到env_str中,否则下一次解析的命令会覆盖环境变量
      strcpy(envstr[env_index], argv[1]);
      //将环境变量导入环境变量表
      putenv(envstr[env_index++]);
    }

4、支持env命令

对于env命令我们只需要写一个打印环境变量表的函数就能完成此命令了。

//main函数的外部 定义一个打印环境变量表的函数

void showEnv()
{
  extern char** environ;
  int i = 0;
  while(environ[i])
  {
    printf("%d : %s\n", i, environ[i++]);
  }
}

//此段代码应该在ls添加颜色之后
 else if(strcmp(argv[0], "env") == 0)
    {
      showEnv();
      continue;
    }

5、支持echo命令

echo命令可以用于打印环境变量,也可以打印退出码,这取决于$后面是不是??我们就可以打印last_status,不是我们就用getenv命令拿到环境变量的内容。文章来源地址https://www.toymoban.com/news/detail-431273.html

//此段代码应该在ls添加颜色之后
else if(strcmp(argv[0], "echo") == 0)
    {
      if(*argv[1] == '$')
      {
        if(*(argv[1] + 1) == '?')
        {
          printf("process exit code %d\n", last_status);
          continue;
        }
        else
        {
          char* str = getenv(argv[1] + 1);
          printf("%s\n",str);
          continue;
        }
      }
    }

到了这里,关于【Linux】教你用进程替换制作一个简单的Shell解释器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java Swing花样玩法:教你用代码制作六一儿童节的精美贺卡(简单版)

    ✨ 博主: 命运之光 ✨ 专栏: Java经典程序设计 前言:这篇博客在打开可能会自动播放视频,视频有音乐,请及时静音哈🙂   目录 ✨前言 ✨引言 ✨简单介绍一下Javaswing这项技术简单介绍一下Javaswing这项技术(选读,感兴趣的可以了解一下哈(●\\\'◡\\\'●)) ✨程序展示 ✨视频

    2024年02月07日
    浏览(40)
  • 【Linux】进程控制 — 进程程序替换 + 实现简易shell

    上一节我们讲了进程终止和进程等待等一系列问题,并做了相应的验证,本章将继续对进程控制进行学习,我们将学习进程程序替换,进行相关验证,运用系统进程程序替换接口,自己模拟写一个shell,该shell能够实现执行指令,等一系列命令行操作…… 概念引入: 将可执行

    2024年02月06日
    浏览(51)
  • 【Linux】Linux进程控制 --- 进程创建、终止、等待、替换、shell派生子进程的理解…

    柴犬: 你好啊,屏幕前的大帅哥or大美女,和我一起享受美好的今天叭😃😃😃 1. 在调用fork函数之后, 当执行的程序代码转移到内核中的fork代码后 ,内核需要分配 新的内存块 和 内核数据结构 给子进程, 内核数据结构包括PCB、mm_struct和页表,然后构建起映射关系 ,同时

    2024年01月16日
    浏览(55)
  • 【linux】进程替换的应用|shell解释器的实现

    当我们学过了进程替换之后,本篇文章可以根据进程替换的知识带你自主实现一个shell命令行 实现步骤 1.显示命令行提示 2.读取输入指令以及对应选项 3.分割第二步的指令以及选项到命令行参数表中 4.处理内建命令 5.进程替换 我们通过观察bash的命令行提示发现他是由三部分

    2024年04月26日
    浏览(50)
  • 【手把手教你制作一个简易版的shell】

    为了简便,命令行中的提示符我们可以直接用printf打印,而具体执行命令可以交给子进程去做,现在的关键是如何将获得的命令行中的命令切割。我们在学习C语言时提到了strtok函数,正好这个函数可以用来作为切割。 基本框架: 不知道大家注意到了没有,我们从键盘中读取

    2023年04月16日
    浏览(50)
  • 制作一个简单的Sifu人物替换Mod

    为什么做 突发奇想 某日看到了sifu mod相关的视频,虽然人刚到静心堂,但是还是想搞花活,于是就开始研究起来。一开始还算顺利,但是后来有一个mod死活安装不上去,游戏启动就是会报错,应该是游戏版本不兼容的问题,所以打算自己研究一下。 虽然过程看起来很简单,

    2023年04月11日
    浏览(46)
  • 教你用JavaScript制作鼠标特效

    欢迎来的我的小院,我是霍大侠,恭喜你今天又要进步一点点了! 我们来用JavaScript编程实战案例,做一个鼠标爱心特效。鼠标在页面移动时会出现彩色爱心特效。通过实战我们将学会createElement方法、appendChild方法、setTimeout方法。 页面出现后,鼠标在页面上移动产生彩色爱心

    2024年02月11日
    浏览(48)
  • 教你用JavaScript制作轮播图

    欢迎来到我的小院,我是霍大侠,恭喜你今天又要进步一点点了! 我们来用JavaScript编程实战案例,做一个轮播图。图片每3秒自动轮换,也可以点击左右按键轮播图片,当图片到达最左端或最右端时,再点击左右键图片弹回最初始的图片或最末尾的图片。通过实战我们将学会

    2024年02月11日
    浏览(53)
  • 教你如何使用Unity制作一个简单的跑酷游戏

    其实用Unity制作游戏并不难,如果你想学习,那我就建议你想从制作一个简单的跑酷游戏来找到兴趣,因为如果你一开始就一直学习一些没什么必要的语法,这样就会让你一开始就失去了信心,失去了学习Unity的动力,所以如果你先学习如何制作一个简单的跑酷地图,然后你就

    2024年02月04日
    浏览(63)
  • 【Unity】教你如何使用Unity制作一个简单的跑酷游戏

    其实用Unity制作游戏并不难,如果你想学习,那我就建议你想从制作一个简单的跑酷游戏来找到兴趣,因为如果你一开始就一直学习一些没什么必要的语法,这样就会让你一开始就失去了信心,失去了学习Unity的动力,所以如果你先学习如何制作一个简单的跑酷地图,然后你就

    2024年02月21日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包