【Linux】模拟实现一个shell
接受每一个人的批评,可是保留你自己的判断。 ——莎士比亚
一段时间的没有更新是由于最近开学期间比较的忙,同时也是由于刚开学的几门课才学习的时候有点迷糊,需要在学校课堂上花的时间更多了,所以才没有更新的,求放过。
简单shell的实现
- 1、shell介绍
- 2、shell实现概括
- 3、shell实现困难
- 4、shell实现具体方式
- 4、1、main函数
- 4、2、MakeCommandLineAndPrint函数
- 4、3、GetUserCommand函数
- 4、4、SplitCommand函数
- 4、5、CheckBuildin函数
- 4、6、ExecuteCommand函数
- 5、总结
1、shell介绍
对于什么是shell问题来说,这是个好问题😊,但是其实如果你看过我之前的文章的话,应该能准确的理解什么是shell,如果想要看之前怎么介绍的话,就会到之前文章里看一看。这里的话就简单讲一下吧,shell简单点来说,就是一个你的老板的一个秘书,这里的老板也能够看作是内核,你想要让你的老板有什么行为的话,你的报告换句话说就是你得将你的命令行代码给到你的老板的秘书,也就是shell,会通过shell来帮助你去找到老板,但是并不是直接就能够找到,并且让他去执行,给到老板前,秘书也会自己考虑一下这个命令行的方式有没有什么不妥的地方,如果有的话也就不会直接麻烦操作系统,这样的话,既保证了内核的安全性,也保证了运行时候的效率,这里的效率提升就是因为能够秘书在接收到几次一样的请求之后能够不再去进行判断,直接否定。
2、shell实现概括
对于shell实现来说,每一次的命令行输入,都会对应着有着一段的运行结果。那对于这种方式来说,可以看作是一个在一个父进程的情况下,一个子进程在不断的执行不同的命令,或者换句话说是在不断的替换进程(其中的环境变量是从父进程传下来的)。
所以我们可以用进程替换的思想去实现一个shell进程(这里的这种进程要一直进行,这样才能够实现执行多次的命令行。
由于我们每次输入的命令行指令都是会被bash读到,然后寻找指定的命令行中提到的程序,然后执行相关的选项。就像这篇文章讲的那样,我们的程序中能够读取到我们输入的东西,所以为什么我们不能够利用这点来实现每次的命令行输入,将对应到进程替换成我们需要的进程,运行结束之后再退出来。
按照这样的方法的话,我就能够奠定了我们实现shell主要实现方向。
3、shell实现困难
1、对于shell来说,不仅仅是读取到我们输入到的命令行是什么,我们还需要在执行之前,每次都会有一段的前置的信息,这一段的前置消息就是,分别对应着用户名,主机名以及当前目录,所以第一个目标就是要解决基本信息的获取以及显示。
2、除此之外,我们还需要将读取到的命令行参数存放在数组之中,所以我们需要根据每一次的用户的命令字符串,切分为不同的字符串数组,其中的要求就是依据空格为分界符号。
3、拆分后,分别的放在一个字符串数组之中。然后进行进程替换,这里的进程替换,选择的函数是execvp,这个在之前的文章中讲述过具体的使用方法,不知道的可以回顾一下,这个进程替换的系统调用函数能够解决我们的问题。
4、当然如果我们知道内建命令,那么我们还需要额外的去实现内建命令构建的操作。
4、shell实现具体方式
4、1、main函数
首先构建一个main函数。
包含一下最主要的函数,最主要的需要实现的功能。
为了方便后续的使用,我们把512定义为一个SIZE,简单的认为这是一个大小的限制(就类似数组大小的限制)。
#define SIZE 512
int main()
{int quit = 0;while(!quit){// 1. 我们需要自己输出一个命令行MakeCommandLineAndPrint();// 2. 获取用户命令字符串char usercommand[SIZE];int n = GetUserCommand(usercommand, sizeof(usercommand));if(n <= 0) return 1;// 3. 命令行字符串分割. SplitCommand(usercommand, sizeof(usercommand));// 4. 检测命令是否是内建命令n = CheckBuildin();if(n) continue;// 5. 执行命令ExecuteCommand();}return 0;
}
4、2、MakeCommandLineAndPrint函数
让每一个命令行都打印出自己的相关的信息。这个函数也不需要传参,因为所有需要得到的都已经存在于环境变量中了。所以为了能够打印相关的信息,就要去读取。所以我们就需要去编写相关函数去编写读取的方法。
首先第一步是构建一个框架。
void MakeCommandLineAndPrint()
{char line[SIZE];const char *uswename=GetUserName();const char *hostname=GetHostName();const char *cwd=GetCwd(); SkipPath(cwd);snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd)==1 ? "/":cwd+1);printf("%s",line);fflush(stdout);
}
其次就是去实现每一个函数的具体意义。
首先我们来看SkipPath
为什么这里会有一个SkipPath呢?难道说每次得到的还不是我们正常使用的cwd吗?那当然不是能够直接使用的啊。所以对于这个函数来说就是为了处理一开始得到的不是我们最终想要的结果。如果不知道原本是什么的话,其实简单说一下也就是从家目录到当前目录的所有的路径都在环境变量的cwd中。所以我们才需要进行额外的处理。为了能够不用多余的函数来增加我们shell的时间复杂度,并且为了能够不传指针就能够实现对于变量的改写,我们需要使用到宏。因为宏是一个能够在编译的时候就能在原本的位置中展开,这也就不会造成重新开栈,重新消耗空间,考虑形参和实参的关系。
#define SkipPath(p) do {p+=(strlen(p)-1); while(*p!='/')p--;} while(0)
这里单独的写出来do{}while,来包含主要的程序,主要的作用是为了防止出现优先级错误的情况。
其中的几个得到环境变量相关信息的函数本质上都是一样的。大概看看应该能够看懂。
const char *GetUserName()
{const char *name = getenv("USER");if(name == NULL) return "None";return name;
}
const char *GetHostName()
{const char *hostname = getenv("HOSTNAME");if(hostname == NULL) return "None";return hostname;
}
const char *GetCwd()
{const char *cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}
这样的话,就能够实现我们编写的shell的第一步了。
4、3、GetUserCommand函数
这个函数的话,是要读取用户输入的字符串,当然用户在输入的时候是有空格的,所以对于该函数,需要注意的是,这里不能够是直接使用scanf函数,而是要找到一个能够按照行来拿到字符串的函数。这样的话才能够保证不会因为存在空格反而不能读到正确的结果。
所以这个函数是什么呢?有没有比较好的一个接口呢?我的建议是选择一个char *fgets(char *s,int size,FILE *stream),如果能够 正确返回,那么返回s的起始位置的地址。如果返回错误,就返回NULL。建议使用这个文件流相关的知识,那是因为之后的文章中马上就要讲解有关于文件流的知识。其中的size指的是s的大小。并且输入的话,存放在的位置是在s中。==其中有一个不注意就会忘记的一点是,我们每次输入的时候按回车才能实现fgets真正的读完,所以说如果我们不干涉的话,在最后会有一个多余的回车。==所以我们需要进行改写,将函数内部传入命令行之后进行sizeof结尾置零操作。
对于这个函数传参的设计的话,应该是需要传入两个。
第一个参数是我们在main函数创建的一个专门存放命令行内容的usercommand数组,这是因为这个数组在读完数据之后还需要进行之后的操作,就比如说分割操作。
第二个参数就是我们用来得到这个字符串所占据的内存大小,因为在fgets函数使用的时候需要用到。
这样的话注意点,以及一些传参的设计都已经搞定了,下面就是真正的代码的实现。
#define ZERO '\0'
int GetUserCommand(char command[], size_t n)
{char *s = fgets(command, n, stdin);if(s == NULL) return -1;command[strlen(command)-1] = ZERO;return strlen(command);
}
4、4、SplitCommand函数
对于分割命令行参数的函数来说,我们需要像之前那样定义一个宏函数来帮助我们实现不用传参的操作吗?其实宏函数确实能够实现,但是对于学习阶段来说我们其实可以想一下,之前在介绍C语言中的字符串函数的时候,有一个函数其实能够刚好符合我们的要求。strtok函数,能够根据特定的字符来找到字符串中每一个字符的位置,如果只执行一次的话,找到的就是第一个要求的字符,如果接着执行的话,就会在第一个基础上往后找。根据函数的这个属性的话,我们就能够利用这个函数从前往后的一次寻找空格来自动帮我们分开字符串。当然找到了符合条件的情况下,就会返回从左到右的第一个子串,后续的会返回第一个结尾之后的第二个位置的子串。如果找不到符合条件的话,就返回NULL。
为什么就是需要我们去实现一个字符串分割为多个呢?那是因为无论未来我们是用什么样子的系统调用的程序替换都需要我们命令行输入的一个一个打散的,而不是整个一起的方式去读取。
其中的NUM是用来默认设置一个命令行参数的个数的,通常情况下来说一个指令后面加上的选项不会超过NUM默认的32个的,如果超过的话,可以自行修改NUM让其能够存放在gArgv[]之中
#define NUM 32
#define SEP " "
char *gArgv[NUM];
void SplitCommand(char command[], size_t n)
{(void)n;// "ls -a -l -n" -> "ls" "-a" "-l" "-n"gArgv[0] = strtok(command, SEP);int index = 1;while((gArgv[index++] = strtok(NULL, SEP))); // done, 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL, 并且while判断结束
}
这里定义的SEP我们需要找到的目标的位置是空格,但是这里非常容易错,那是因为strtok函数中的第二个参数是字符串而不是字符。
4、5、CheckBuildin函数
内建命令的特点就是不需要考虑当前环境或者是默认的配置的条件,在什么地方shell都能够运行出来相对于的结果。
对于现在的我来说我只认识两个内建命令。分别是cd命令,echo $?命令。这两个我在之前讲环境变量的时候讲述过了其特点。所以要想这两个命令的与众不同,肯定是在函数结构上的与众不同。就比如之前的一些命令的话会存在于bin目录之下,但是内建命令可能就直接存在程序之中,这样的话,不会受到环境的因素也能够实现相对应的指令。
所以根据内建命令的特点,我写了一个检查内建命令的函数,如果满足条件的话就会直接运行,不会先替换进程然后执行,这样就能够避免环境改变造成无法执行相关功能的问题。
函数的返回值设置为int类型,这样做的话能够判断是否用户输入的为内建命令,如果是内建命令的话,就会执行完,也就不会再去执行下一个的ExecuteCommand函数。避免了重复执行的错误。
char cwd[SIZE*2];
int lastcode = 0;
const char *GetHome()
{const char *home = getenv("HOME");if(home == NULL) return "/";return home;
}
void Cd()
{const char *path = gArgv[1];if(path == NULL) path = GetHome();//如果是空的话,会在直接返回家目录// path 一定存在chdir(path);// 刷新环境变量char temp[SIZE*2];getcwd(temp, sizeof(temp));snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd); // OK
}int CheckBuildin()
{int yes = 0;const char *enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0){yes = 1;Cd();}else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){yes = 1;printf("%d\n", lastcode);lastcode = 0;}return yes;
}
这里容易错的地方就是环境变量也是需要更新的,不能说我们进行了好几次的cd或者其他命令之后环境变量因为没有进行更新从而错误。
这样的话能够实现简单的内建命令。那我们该怎么去执行内建命令之外的命令呢?当然是使用进程替换!
4、6、ExecuteCommand函数
进程替换,那就是说在该函数中需要使用到fork()函数,并且还需要判断使用哪一个系统调用函数来确定传参条件。考虑之后还是使用execvp函数。下面是实现的代码。
void ExecuteCommand()
{pid_t id = fork();if(id < 0) Die();else if(id == 0){// childexecvp(gArgv[0], gArgv);exit(errno);}else{// fahterint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);}}
}
5、总结
这样的话就简单的把一个shell的指令完全的自我实现了,其中当然也会有很多的不足的地方,但是基本上的内容都已经实现。希望读者能够在本篇文章的基础之上,学到更多,理解过多的关于shell编程的快乐,也希望能够通过该篇文章,给自己的学习路上添砖加瓦,做到更好。
相关文章:
【Linux】模拟实现一个shell
接受每一个人的批评,可是保留你自己的判断。 ——莎士比亚 一段时间的没有更新是由于最近开学期间比较的忙,同时也是由于刚开学的几门课才学习的时候有点迷糊,需要在学校课堂上花的时间更多了,所以才没有更新的,求放过…...
云原生数据库 PolarDB
简介:云原生数据库 PolarDB 是阿里云自研产品,在存储计算分离架构下,利用了软硬件结合的优势,为用户提供秒级弹性、高性能、海量存储、安全可靠的数据库服务。100%兼容MySQL和PostgreSQL生态,支持分布式扩展࿰…...
MobaXterm基本使用 -- 服务器状态、批量操作、显示/切换中文字体、修复zsh按键失灵
监控服务器资源 参考网址:https://www.cnblogs.com/144823836yj/p/12126314.html 显示效果 MobaXterm提供有这项功能,在会话窗口底部,显示服务器资源使用情况 如内存、CPU、网速、磁盘使用等: (完整窗口࿰…...
elastic Search 初步之向量检索的数据写入及检索查询
### Elasticsearch 向量检索实现方法方案 Elasticsearch 从 7.3 版本开始引入了向量检索功能,支持通过向量字段进行相似度搜索。以下是实现向量检索的步骤和方案,包括 Python 和 Java 版本的代码示例。 #### 1. 最低实现向量检索的 ES 版本 - **最低版本**: Elasticsearch …...
Tdesign TreeSelect 树形选择 多选
这里写自定义目录标题 小程序原生开发 Tdesign TreeSelect 树形选择 多选可以选择不同一级分类下的数据 小程序原生开发 Tdesign TreeSelect 树形选择 多选可以选择不同一级分类下的数据 TreeSelect 树形选择 在原demo基础上修改 const chineseNumber 一二三四五六七八九十.…...
Pygame中Sprite实现逃亡游戏5
在《Pygame中Sprite实现逃亡游戏4》中通过碰撞检测实现了玩家、飞龙与飞火之间的碰撞处理,基本上实现了逃亡功能。最后,实现这个逃亡游戏中文字提示的功能。 1 操作提示 当进入游戏后,会在玩家下方的位置给出操作提示,如图1所示…...
等保2.0数据库测评之达梦数据库测评
一、达梦数据库介绍 达梦数据库管理系统属于新一代大型通用关系型数据库,全面支持 ANSI SQL 标准和主流编程语言接口/开发框架。行列融合存储技术,在兼顾 OLAP 和 OLTP 的同时,满足 HTAP 混合应用场景。 本次安装环境为Windows10专业版操作…...
集成mcuboot后测试和验证的方法
本文介绍一些在实际项目中集成的 MCUboot后测试和验证的方法和步骤: 功能测试 启动测试 正常启动验证 : 多次上电启动设备,观察 MCUboot 是否能够正常加载并跳转到应用程序。检查启动过程中的日志输出(如果有)&#…...
Vulhub zico 2靶机详解
项目地址 https://download.vulnhub.com/zico/zico2.ova实验过程 将下载好的靶机导入到VMware中,设置网络模式为NAT模式,然后开启靶机虚拟机 使用nmap进行主机发现,获取靶机IP地址 nmap 192.168.47.1-254根据对比可知Zico 2的一个ip地址为…...
宠物医院微信小程序源码
文章目录 前言研究背景研究内容一、主要技术?二、项目内容1.整体介绍(示范)2.系统分析3.数据表信息4.运行截图5.部分代码介绍 总结 前言 随着当代社会科技的迅速发展,计算机网络时代正式拉来帷幕,它颠覆性的影响着社会…...
[教程]Crystal源码下载及编译
描述: 随着 Crystal Source 代码的更新,用于构建源代码和编译它们的指南已经过时,这导致了很多混淆和寻求帮助。 本指南将是一个完整的分步指南,从下载 Visual Studio 到启动到您的服务器。 此外,请确保下载此存储库中…...
【Android 14源码分析】WMS-窗口显示-流程概览与应用端流程分析
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。 – 服装…...
双指针---(部分地更新)
双指针 复写零 给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。 注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。 …...
【Windows】自定义显示器的分辨率
背景 由于本人更新驱动导致2个显示器里面,有一个显示器的分辨率只剩下2个可以调节 这样就导致2个显示器分辨率不同,更新了多次驱动都修复不了,所以想着看能不能自定义分辨率 工具下载 显示器自定义分辨率工具 或者百度搜索 Custom Resolu…...
组播基础-2-IGMP协议
文章目录 IGMPIGMPv1IGMPv2IGMPv3IGMP总结IGMP Snooping IGMP 运行于主机和路由器之间 因特网组管理协议,TCP/IP 协议族中负责 IP 组播成员管理的协议,用来在接收者与其他直接相邻的组播路由器之间建立、维护组播组成员关系 负责组播成员管理…...
基于Springboot+Vue的视频点播系统设计与实现登录 (含源码数据库)
1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统中…...
执行力怎么培养?
执行力怎么培养? 并行:适合在初期养成习惯,不抱对结果的期望天才就是强迫症:适合中期修身:适合高级 并行:适合在初期养成习惯,不抱对结果的期望 在你开始做任何事情的时候,不要一开…...
Power apps:一次提交多项申请
1、添加一个Form,导入sharepoint列表,添加确认,继续,取消按钮 2、在页面的onvisible属性中添加 Set(applynumber,Last(付款申请表).申请编号1); #定义一个申请编号变量,每次申请,就将列表最后一个…...
Oracle数据库物理结构操作管理
实验步骤 (1)查询数据库初始化参数中参数名包含sga的参数的名称、值和描述信息。 SQL> select name,value,description from V$PARAMETER where name like %sga%; (2)设置sga_max_size的大小为1G SQL> alter system set sg…...
Python自然语言处理之spacy模块介绍、安装与常见操作案例
文章目录 spacy模块介绍安装spacy常见操作案例及代码1. 加载模型并处理文本2. 词性标注3. 命名实体识别4. 依存句法分析5. 可视化(在Jupyter Notebook中) spacy模块介绍 spacy是一个强大的Python库,用于自然语言处理(NLP…...
DSPy101
DSPy 介绍 DSPy(Declarative Self-improved Language Programs in Python) 是一个用于系统化和增强在流水线内使用语言模型的框架,它通过数据驱动和意图驱动的系统来优化大型语言模型(LLM)的使用。 DSPy 的核心是模块…...
网格交易策略:从原理、应用到实战Python回测
01 引言 随着金融市场的快速发展,量化交易成为投资者追求收益的一种重要手段。在众多的量化交易策略中,网格交易策略(Grid Trading Strategy)因其简单易用、风险控制灵活等优点而备受青睐。网格交易策略的核心思想是“低买高卖”&…...
软考论文《论大数据处理架构及其应用》精选试读
论文真题 模型驱动架构设计是一种用于应用系统开发的软件设计方法,以模型构造、模型转换和精化为核心,提供了一套软件设计的指导规范。在模型驱动架构环境下,通过创建出机器可读和高度抽象的模型实现对不同问题域的描述,这些模型…...
fatfs API使用手册
配置 /*---------------------------------------------------------------------------/ / Configurations of FatFs Module /---------------------------------------------------------------------------*/#define FFCONF_DEF 80286 /* Revision ID *//*---------------…...
9.23作业
仿照string类,自己手动实现 My_string 代码如下 MyString.h #ifndef MYSTRING_H #define MYSTRING_H #include <iostream> #include <cstring>using namespace std;class My_string { private:char *ptr; //指向字符数组的指针int size; …...
Unity3D 房间去重叠化算法详解
前言 在Unity3D游戏开发中,经常需要生成和处理多个房间的场景,特别是在地牢生成、房屋布局或迷宫设计等应用中。为了确保生成的房间不会重叠,我们需要一种有效的去重叠化算法。以下将详细介绍该算法的原理和代码实现。 对惹,这里有…...
mybatis 配置文件完成增删改查(五) :单条件 动态sql查询,相当于switch
文章目录 单条件 动态sql查询写测试方法 疑问总结 单条件 动态sql查询 <select id"selectByConditionBySingle" resultMap"brandResultMap">.select *from tb_brandwhere<choose>/*相当于switch*/<when test"status ! null">…...
全球IP归属地查询-IP地址查询-IP城市查询-IP地址归属地-IP地址解析-IP位置查询-IP地址查询API接口
IP地址城市版查询接口 API是指能够根据IP地址查询其所在城市等地理位置信息的API接口。这类接口在网络安全、数据分析、广告投放等多个领域有广泛应用。以下是一些可用的IP地址城市版查询接口API及其简要介绍 1. 快证 IP归属地查询API 特点:支持IPv4 提供高精版、…...
Vue3+FastAPI中Token的刷新机制(含代码示例)
在Vue3和FastAPI的应用中,token刷新机制通常涉及以下几个步骤: 登录过程:用户登录时,后端FastAPI验证用户信息,验证通过后生成一个访问令牌(access token)和一个刷新令牌(refresh t…...
【GAN 图像生成】
理论知识学习: PART 1: 生成对抗网络GAN 深度学习模型,用于生成数据 对抗式训练,生成器v判别器 DCGAN>WGAN>StyleGAN技术不断进化 GAN在艺术创作。数据增强领域应用越来越广泛 应用: GAN在图像合成&#x…...
ios wordpress连接站点/江苏网站推广
请下载安装文档:https://github.com/lpx20181019/document...
如何做网站顶级域名/企业网络营销顾问
0、导读 2016年最新开发语言排行榜中,Python已经跃居第三,仅次于C、JAVA。掌握Python已经成为时下运维圈的共识,更让人期待的是,本次公开课分享的嘉宾自身就长期专注Python、Docker技术,非常值得期待。 1、活动总结 本…...
分销系统什么意思/安徽seo网络优化师
简单的C语言编辑器课程设计《编译原理》课程设计简单的C语言编译器6 -扬 州 大 学编译原理课程设计报告题 目题 目 简单的编译器班 级 计科0802班学 号 081202427姓 名 张 俊指导教师 姜 卯 生成 绩扬州大学信息工程学院2011课程设计题目实现一个简单的编译器课程设计目的通过编…...
企业网站设计经典案例/在线代理浏览网址
题目 力扣 思路 暴力 根据题意,在sx和sy不大于tx和ty时,遍历所有情况,结果会超时。 代码 class Solution { public:bool reachingPoints(int sx, int sy, int tx, int ty) {return dfs(sx, sy, tx, ty);}bool dfs(int sx, int sy, int t…...
网络服务器无响应原因/爱站seo工具包
何为协程 协程,又称微线程。英文名Coroutine。 协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协…...
易语言做网站登录/域名服务器ip地址查询
趣味跑步比赛手机版是一款非常的魔性的休闲跑步小游戏,靓丽的颜色组成了精彩的游戏画面,把玩家带进这个有趣的世界,充满了想象力的那些脑洞大开的跑道的设计十分的好玩,让玩家在不用于一般的赛道之上比赛冲满了惊险和刺激…...