当前位置: 首页 > news >正文

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

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

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

制作一个简单的Shell解释器

  • 一、观察Shell的运行状态
  • 二、简单的Shell解释器制作原理
    • 1、获取命令行
    • 2、解析命令行
    • 3、创建子进程 进行程序替换 父进程等待
    • 4、实际运行
  • 二、对简单的内建命令进行处理
    • 1、给ls命令加上色彩
    • 2、支持cd命令
    • 3、支持export命令
    • 4、支持env命令
    • 5、支持echo命令


一、观察Shell的运行状态

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

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

在这里插入图片描述

我们仔细进行分析可以发现,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 128int 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\0commandstr[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;}

在这里插入图片描述

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

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

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

//main函数内部,while循环上面定义退出码变量int last_status = 0;//while循环内部//创建子进程,进行命令处理pid_t id = fork();assert(id >= 0);if(id == 0){//child processexecvp(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命令 看一看效果。
在这里插入图片描述

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

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

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

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

1、给ls命令加上色彩

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

在这里插入图片描述
我们在运行我们自己制作的Shell时也可以加上--color=auto

//此段代码应该在切割字符串之后//argv[0]就是我们的命令名
if(strcmp(argv[0], "ls") == 0){int pos = 0;//寻找指针数组的结尾while(argv[pos++]);//在NULL位置加上 --color=autoargv[pos - 1] = "--color=auto";//将后一个位置置空argv[pos] = NULL;}

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

2、支持cd命令

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

在这里插入图片描述

//此段代码应该在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命令拿到环境变量的内容。

//此段代码应该在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解释器

本章的代码可以访问这里获取。 由于程序代码是一体的&#xff0c;本章在分开讲解各部分的实现时&#xff0c;代码可能有些跳跃&#xff0c;建议在讲解各部分实现后看一下源代码方便理解程序。 制作一个简单的Shell解释器 一、观察Shell的运行状态二、简单的Shell解释器制作原理…...

onMeasure里如何重置只有1个子view一行满屏, 若有多个自适应一行

onMeasure里如何重置只有1个子view一行满屏, 若有多个自适应一行 可以尝试在 onMeasure 方法中重写 measureChildWithMargins 或 measureChild 方法来实现这个需求。 对于只有一个字的 View,我们可以把它的宽度设为屏幕宽度,高度设为最大高度,这样这个 View 就会占满一整行…...

Postman创建项目 对接口发起请求处理

查看本文之前 您需要理解了解 Postman 的几个简单工作区 如果还没有掌握 可以先查看我的文章 简单认识 Postman界面操作 那么 掌握之后 我们就可以正式来开启我们的接口测试 我们先选择 Collections 我们点上面这个加号 多拉一个项目出来 然后 我们选我们刚加号点出来的项目…...

在Vue3项目中js-cookie库的使用

文章目录 前言1.安装js-cookie库2.引入、使用js-cookie库 前言 今天分享一下在Vue3项目中引入使用js-cookie。 1.安装js-cookie库 js-cookie官网 安装js-cookie&#xff0c;输入 npm i js-cookie安装完成可以在package.json中看到&#xff1a; 安装以后&#xff0c;就可…...

【论文笔记】Attention和Visual Transformer

Attention和Visual Transformer Attention和Transformer为什么需要AttentionAttention机制Multi-head AttentionSelf Multi-head Attention&#xff0c;SMA TransformerVisual Transformer&#xff0c;ViT Attention和Transformer Attention机制在相当早的时间就已经被提出了&…...

独立IP服务器和共享IP服务器有什么区别

在选择一个合适的服务器时&#xff0c;最常见的选择是共享IP服务器和独立IP服务器。尽管两者看起来很相似&#xff0c;但它们有着很大的不同。本文将详细介绍共享IP服务器和独立IP服务器的不同之处&#xff0c;以及如何选择适合您需求的服务器。 一、什么是共享IP服务器? 共享…...

Java8

Java8 &#xff08;一&#xff09;、双列集合&#xff08;二&#xff09;、Map集合常用api&#xff08;三&#xff09;、Map集合的遍历方式&#xff08;四&#xff09;、HashMap&#xff08;五&#xff09;、LinkedHashMap&#xff08;六&#xff09;、TreeMap&#xff08;七&a…...

nn.conv1d的输入问题

Conv1d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue) in_channels(int) – 输入信号的通道。在文本分类中&#xff0c;即为词向量的维度out_channels(int) – 卷积产生的通道。有多少个out_channels&#xff0c;就需要多少个1维…...

js判断是否为null,undefined,NaN,空串或者空对象

js判断是否为null&#xff0c;undefined&#xff0c;NaN&#xff0c;空串或者空对象 这里写目录标题 js判断是否为null&#xff0c;undefined&#xff0c;NaN&#xff0c;空串或者空对象特殊值nullundefinedNaN空字符串&#xff08;""&#xff09;空对象&#xff08;…...

Java每日一练(20230501)

目录 1. 路径交叉 &#x1f31f;&#x1f31f; 2. 环形链表 &#x1f31f;&#x1f31f; 3. 被围绕的区域 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏…...

从零开始学习Web自动化测试:如何使用Selenium和Python提高效率?

B站首推&#xff01;2023最详细自动化测试合集&#xff0c;小白皆可掌握&#xff0c;让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 引言&#xff1a; 一、了解Web自动化测试的基本概念 二、选择Web自动化测试工具 三、学习Web自动化测试的…...

fastdfs环境搭建

安装包下载路径 libfastcommon下载地址&#xff1a;https://github.com/happyfish100/libfastcommon/releasesFastDFS下载地址&#xff1a;https://github.com/happyfish100/fastdfs/releasesfastdfs-nginx-module下载地址&#xff1a;https://github.com/happyfish100/fastdf…...

有什么牌子台灯性价比高?性价比最高的护眼台灯

由心感叹现在的孩子真不容易&#xff0c;学习压力比我们小时候大太多&#xff0c;特别是数学&#xff0c;不再是简单的计算&#xff0c;而更多的是培养学生其他思维方式&#xff0c;有时候我都觉得一年级数学题是不是超纲了。我女儿现在基本上都是晚上9点30左右上床睡觉&#x…...

信息系统项目管理师 第9章 项目范围管理

1.管理基础 1.产品范围和项目范围 产品范围:某项产品、服务或成果所具有的特征和功能。根据产品需求来衡量。 项目范围:包括产品范围&#xff0c;是为交付具有规定特性与功能的产品、服务或成果而必须完成的工作。项目管理计划来衡量 2.管理新实践 更加注重与商业分析师一起…...

【Android入门到项目实战-- 8.2】—— 使用HTTP协议访问网络

目录 一、使用HttpURLConnection 1、使用Android的HttpURLConnection步骤 1&#xff09;获取HttpURLConnection实例 2)设置HTTP请求使用的方法 3)定制HTTP请求&#xff0c;如连接超时、读取超时的毫秒数 4)调用getInputStream()方法获取返回的输入流 5)关闭HTTP连接 2、…...

Go官方指南(五)并发

Go 程 Go 程&#xff08;goroutine&#xff09;是由 Go 运行时管理的轻量级线程。 go f(x, y, z) 会启动一个新的 Go 程并执行 f(x, y, z) f, x, y 和 z 的求值发生在当前的 Go 程中&#xff0c;而 f 的执行发生在新的 Go 程中。 Go 程在相同的地址空间中运行&#xff0c…...

VS快捷键大全 | 掌握这些快捷键,助你调试快人一步

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…...

【刷题】203. 移除链表元素

203. 移除链表元素 一、题目描述二、示例三、实现方法1-找到前一个节点修改next指向方法2-不是val的尾插重构 总结 203. 移除链表元素 一、题目描述 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新…...

C++11学习- CPU多核与多线程、并行与并发

随着计算机编程频繁使用&#xff0c;关于CPU的处理性能的讨论从未停止过&#xff0c;由于我最近在学习多线程相关的知识&#xff0c;那么就来理一理CPU的核心问题。 一、线程与进程 业解释 线程是CPU调度和分配的基本单位&#xff0c;可以理解为CPU只看得到线程&#xff1b; …...

docker登录harbor、K8s拉取镜像报http: server gave HTTP response to HTTPS client

docker登录harbor、K8s拉取镜像报http: server gave HTTP response to HTTPS client 当搭建完docker私有仓库后&#xff0c;准备docker login http://ip:端口 登录时会包如下错误 当我们使用docker私有仓库中的镜像在K8s集群中部署应用时会包如下错误 以上错误根据报错信息可…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...