【Linux】简易版shell

在这里插入图片描述

文章目录

  • shell的基本框架
  • PrintCommandLine
  • GetCommandLine
  • ParseCommandLine
  • ExecuteCommand
  • InitEnv
  • CheckAndExecBuildCommand
  • 代码总览
  • 运行效果
  • 总结

shell的基本框架

要写一个命令行我们首先要写出基本框架。

  1. 打印命令行
  2. 获取用户输入的命令
  3. 分析命令
  4. 执行命令

基本框架的代码:

int main()
{//because shell must keep runningwhile(true){PrintCommandLine();  //1.use this func to print command line//only get <command_buffer ->outputGetCommandLine();    //2.get user's command //"is -a -b -c -d"--->"ls" "-a" "-b" "-d"ParseCommandLine();  //3.analyze commandExecuteCommand();    //4.implement command}return 0;
}

因为命令要时刻保持运行,所以我们还要加上一层循环保证时时刻刻在刷新命令行。

PrintCommandLine

接下来就需要实现打印命令行的函数了,首先看一下我们打印命令行时需要什么?
在这里插入图片描述
需要用户名,主机名,文件路径最后还需要一个$或者#

void PrintCommandLine()  //1.use this func to print command line
{//create command line //this printf have no \n,so this result is won't be display immediatelyprintf("%s",MakeCommandLine().c_str());
}

打印的这个函数,MakeCommandLine()负责返回一个string类型的表。
由于我们要获取主机名,所以需要用到获取环境变量的函数,getenv()

//get user name 
string GetUserName()
{string name =getenv("USER");return name.empty()?"None":name;
}//get hostname 
string GetHostName()
{string HostName=getenv("HOSTNAME");return HostName.empty()?"None":HostName;
}
const int basesize=1024;
//overall situation's The working path of the current shell
char Pwd[basesize];
//overall situation's Pwd envitonment variable
char Pwdenv[basesize];
//get pwd 
string GetPwd()
{//string Pwd=getenv("PWD");if(nullptr == getcwd(Pwd,sizeof(Pwd))) return "None";snprintf(Pwdenv,sizeof(Pwdenv),"PWD=%s",Pwd);putenv(Pwdenv);return Pwd;
}

由于这里不能直接使用getenv(“PWD”),因为这里获取的是shell的pwd,shell的pwd一直都处在当前路径下,也就是我们的自己的shell运行的那个目录下,所以这里,这里前两个函数都很容易理解,只需要解释一下最后一个,我们来看看snprintf这个函数
在这里插入图片描述
这个函数是将后面的一个字符串以某种格式打印到前面的s当中,需要写出前面s的大小。
在这里插入图片描述
回到获取路径这个函数当中,第一个if是用来判断获取当前工作路径是否成功。如果获取成功,当前工作路径将存储在Pwd当中,snprintf,这个函数我们将Pwd这个字符串以PWD="Pwd"这样的格式打印这个,所以这里Pwdenv已经存储了环境变量的那个格式,获取了环境变量瞬时应该将环境变量表中的环境变量更新一下,所以putenv,最后将当前工作路径返回即可。
下面函数只需要调用上面获取好的接口即可,然后按照格式化打印,将对应的用户名,型号名,还有工作路径,都存储在Command_Line这个缓冲区中,然后返回到打印的函数

string MakeCommandLine()
{//[newuser@hcss-ecs-e091 myshell]$char Command_Line[basesize];//outputsnprintf(Command_Line,basesize,"[%s@%s %s]# ",GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());return Command_Line;
}

PrintCommandLine
这里打印的时候需要刷新的一下屏幕,保证一直都是在一行

void PrintCommandLine()  //1.use this func to print command line
{//create command line //this printf have no \n,so this result is won't be display immediatelyprintf("%s",MakeCommandLine().c_str());//refresh screenfflush(stdout);
}

GetCommandLine

获取命令行,首先需要一个数组,将输入的命令先存放在这个函数中,然后由下一步来执行将字符串拆分为单个命令和选项即可。

//after only call this commandline,put string to this buffer
bool GetCommandLine(char Command_Buffer[],int size)    //2.get user's command 
{//cannot be use scanf and cin //we think:We need to treat the command line entered by the user as a complete string //"ls -a -l -n" this is a complete string //              array        size   stadard inputchar *result = fgets(Command_Buffer,size,stdin);if(!result) return false;//we should delete last str,because it is enter keyCommand_Buffer[strlen(Command_Buffer)-1] = 0;//currently, it is OKif(strlen(Command_Buffer) == 0) return false;return true;
}

这里不能用scanf,因为scanf和cin不能使用空格,所以我们选择用fgets,用fgets获取字符串,然后将这个字符串存在Command_Buffer中,获取完之后判断一下是否获取成功,就是检查一下获取之后的变量是否是nullptr。

注意:最后一个位置的字符需要改为0,因为我们输入的时候,会回车也是一个字符,所以应该将这个回车给去掉
当获取的命令中只有回车的时候,将回车去掉strlen就变为0了,所以只有回车时不需要解析命令,所以直接返回false。

ParseCommandLine

获取成功后我们就需要将整个字符串以空格为分隔符将其分为各个字符串来进行解析了。
这里转化为的表在shell中就是传给main参数的argc和argv,所以这里我们也需要一个变量argc和一个argv一个来计数一个来存储解析出来的表。

const int argvnum = 64;
//command line parameter list 
char *gargv[argvnum];
//for counting
int gargc;

由于我们使用的是全局变量,所以每次进入这个函数的时候都需要重置这两个全局变量

void ParseCommandLine(char Command_Buffer[],int len)  //3.analyze command
{(void)len;memset(gargv,0,sizeof(gargv));gargc=0;//"ls -a -l -n"---->ls//finally cut to Cogargv const char *sep = " ";//post++gargv[gargc++] = strtok(Command_Buffer,sep);//Form a table and stop looping when the return value is nullptr while((bool)(gargv[gargc++] = strtok(nullptr,sep)));gargc--;
}

在这里插入图片描述
这个函数是将str以delimiters为分隔符来分割字符串,分出来的字符串会返回首地址,第一个参数只有第一次才传入对应的字符串的首地址,往后调用这个函数对同一个字符串做分割,只需要传入nullptr,所以第一个较为特殊,我们只需要对第一个做特殊处理,将其第一个分割,然后存储在gargv中,然后对应的计数++,往后都是nullptr作为第一个参数,往后分割之后返回的是nullptr就证明分割完了,所以这里我们要使用一个循环,但是由于nullptr那次也进行了++,所以实际上计数多记了一次,下面要进行–。
在这里插入图片描述

ExecuteCommand

将对应的字符串根据空格分隔符翻译为表之后,接下来就需要执行命令了,为了确保shell的稳定,所以我们用子进程来执行命令,这里创建子进程,然后判断子进程是否创建成功,创建成功后,子进程执行任务,这里最开始其实可以用execvp来进行进程替换,gargv是指令,gargv是整个选项。
在这里插入图片描述
由于进行进程替换之后就不可能执行exit了,所以exit是用来判断替换失败还是替换成功的,如果替换成功就不会exit了,如果失败就会退出,并且退出码是1,父进程等待子进程结束,回收子进程即可,如果rid为正说明回收成功,如果rid小于零等待失败直接返回false

bool ExecuteCommand()    //4.implement command
{//implement command //let the child process execute //because parent process execute the process ,if this process is failed ,myshell is hangspid_t id = fork();//create childif(id < 0) return false;if(id == 0){//child process//implement command execvpe(gargv[0],gargv,genv);//Exitexit(1);//fail return 1}int status = 0;pid_t rid = waitpid(id,&status,0);//blocking wait if(rid > 0){//wait successreturn true;}else return false;
}

这里其实已经差不多了,但是我们还需要初始化我们的环境变量表。

InitEnv

需要顶一个全局的环境变量表

//my env array 
const int envnum = 64;
char *genv[envnum];

拥有我们自己的环境变量只需要将父进程的环境变量拷贝下来即可,进行深拷贝。

//as a shell,to get a evironment variable should from system to get 
//today, we direct get environment variable from parent process
void InitEnv()
{//get environment variables from parent process extern char **environ;int index = 0;while(environ[index] != nullptr){//open up the same space as environment variable genv[index] =(char*)malloc(strlen(environ[index])+1);//copy element in genv to environ strncpy(genv[index],environ[index],strlen(environ[index]+1));index++;}genv[index]=nullptr;
}

我们还需要对一些命令进行特殊处理,比如一些内建命令。

CheckAndExecBuildCommand

因为我们是用子进程执行的命令,所以如果我们cd的话,是子进程cd,影响不了父进程,子进程执行完cd直接退出了,所以我们需要一个函数来判断这个命令是否是内建命令
内建命令是指直接在 shell 内部实现的命令,而不是外部可执行文件。内建命令是由 shell 本身提供和执行的,因此它们不需要创建新的进程来执行。相比于外部命令,内建命令的执行速度更快,因为它们不需要通过系统调用加载可执行文件。内建命令通常用于控制 shell 的行为或执行与 shell 相关的任务。

//shell execute command by itself,the essence is shell call itself's func
bool CheckAndExecBuildCommand()//check build-in command and execute command 
{if(strcmp(gargv[0],"cd") == 0){//build-in commandif(gargc == 2){//change path chdir(gargv[1]);}return true;}//export is also a build-in command else if(strcmp(gargv[0],"export") == 0){if(gargc == 2){AddEnv(gargv[1]);}return true;}//env is also a build-in command else if(strcmp(gargv[0],"env") == 0){for(int i = 0;genv[i];i++){printf("%s\n",genv[i]);}return true;}return false;
}

代码总览

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<cstdlib>
using namespace std;const int basesize=1024;
const int argvnum = 64;
//command line parameter list 
char *gargv[argvnum];//for counting
int gargc;//overall situation's The working path of the current shell
char Pwd[basesize];
//overall situation's Pwd envitonment variable
char Pwdenv[basesize];//my env array 
const int envnum = 64;
char *genv[envnum];//get user name 
string GetUserName()
{string name =getenv("USER");return name.empty()?"None":name;
}//get hostname 
string GetHostName()
{string HostName=getenv("HOSTNAME");return HostName.empty()?"None":HostName;
}//get pwd 
string GetPwd()
{//string Pwd=getenv("PWD");if(nullptr == getcwd(Pwd,sizeof(Pwd))) return "None";snprintf(Pwdenv,sizeof(Pwdenv),"PWD=%s",Pwd);putenv(Pwdenv);return Pwd;
}//Create output format 
string MakeCommandLine()
{//[newuser@hcss-ecs-e091 myshell]$char Command_Line[basesize];//outputsnprintf(Command_Line,basesize,"[%s@%s %s]# ",GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());return Command_Line;
}void PrintCommandLine()  //1.use this func to print command line
{//create command line //this printf have no \n,so this result is won't be display immediatelyprintf("%s",MakeCommandLine().c_str());//refresh screenfflush(stdout);
}//after only call this commandline,put string to this buffer
bool GetCommandLine(char Command_Buffer[],int size)    //2.get user's command 
{//cannot be use scanf and cin //we think:We need to treat the command line entered by the user as a complete string //"ls -a -l -n" this is a complete string //              array        size   stadard inputchar *result = fgets(Command_Buffer,size,stdin);if(!result) return false;//we should delete last str,because it is enter keyCommand_Buffer[strlen(Command_Buffer)-1] = 0;//currently, it is OKif(strlen(Command_Buffer) == 0) return false;return true;
}void ParseCommandLine(char Command_Buffer[],int len)  //3.analyze command
{(void)len;memset(gargv,0,sizeof(gargv));gargc=0;//"ls -a -l -n"---->ls//finally cut to Cogargv const char *sep = " ";//post++gargv[gargc++] = strtok(Command_Buffer,sep);//Form a table and stop looping when the return value is nullptr while((bool)(gargv[gargc++] = strtok(nullptr,sep)));gargc--;
}//in my command line 
//have some command must be child process to implement
//but have some command not be child process to implement----built-in command 
bool ExecuteCommand()    //4.implement command
{//implement command //let the child process execute //because parent process execute the process ,if this process is failed ,myshell is hangspid_t id = fork();//create childif(id < 0) return false;if(id == 0){//child process//implement command execvpe(gargv[0],gargv,genv);//Exitexit(1);//fail return 1}int status = 0;pid_t rid = waitpid(id,&status,0);//blocking wait if(rid > 0){//wait successreturn true;}else return false;
}//add a environment variable 
void AddEnv(const char *item)
{int index = 0;while(genv[index]) index++;//find last location genv[index] = (char*)malloc(strlen(item)+1);strncpy(genv[index],item,strlen(item)+1);genv[++index] = nullptr;}//shell execute command by itself,the essence is shell call itself's func
bool CheckAndExecBuildCommand()//check build-in command and execute command 
{if(strcmp(gargv[0],"cd") == 0){//build-in commandif(gargc == 2){//change path chdir(gargv[1]);}return true;}//export is also a build-in command else if(strcmp(gargv[0],"export") == 0){if(gargc == 2){AddEnv(gargv[1]);}return true;}//env is also a build-in command else if(strcmp(gargv[0],"env") == 0){for(int i = 0;genv[i];i++){printf("%s\n",genv[i]);}return true;}return false;
}//as a shell,to get a evironment variable should from system to get 
//today, we direct get environment variable from parent process
void InitEnv()
{//get environment variables from parent process extern char **environ;int index = 0;while(environ[index] != nullptr){//open up the same space as environment variable genv[index] =(char*)malloc(strlen(environ[index])+1);//copy element in genv to environ strncpy(genv[index],environ[index],strlen(environ[index]+1));index++;}genv[index]=nullptr;
}int main()
{//my shell's environment variable InitEnv();//new buffer char Command_Buffer[basesize];//because shell must keep runningwhile(true){PrintCommandLine();  //1.use this func to print command line//only get <command_buffer ->outputif(!GetCommandLine(Command_Buffer,basesize))    //2.get user's command {//get fail continue; }//"is -a -b -c -d"--->"ls" "-a" "-b" "-d"ParseCommandLine(Command_Buffer,strlen(Command_Buffer));  //3.analyze commandif(CheckAndExecBuildCommand()){continue;}ExecuteCommand();    //4.implement command}return 0;
}

运行效果

在这里插入图片描述

总结

通过编写一个简易版的Linux命令行shell,我们掌握了在命令行环境中解析并运行指令的基础知识。这一项目帮助我们理解了如何通过系统调用执行外部程序、处理输入和输出,以及如何让shell与用户交互。尽管功能较为基础,但它包含了命令读取、解析和执行等关键流程,为后续学习更复杂的shell实现和系统编程提供了扎实的基础。如果有兴趣进一步扩展,可以尝试加入更多特性,如命令历史记录、自动补全、管道和重定向支持等,使这个shell更加功能丰富。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/8841.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Java 枚举

目录 枚举是什么 常用方法 构造方法 枚举的优缺点 枚举和反射 实现单例模式 枚举是什么 枚举&#xff08;enum&#xff09;&#xff1a;是一种特殊的类&#xff0c;用于定义一组常量&#xff0c;将其组织起来。枚举使得代码更具有可读性和可维护性&#xff0c;特别是在处…

【梯度下降法优化】随机梯度下降、牛顿法、动量法、Nesterov、AdaGrad、RMSprop、Adam

本文理论参考王木头的视频&#xff1a; “随机梯度下降、牛顿法、动量法、Nesterov、AdaGrad、RMSprop、Adam”&#xff0c;打包理解对梯度下降法的优化_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1r64y1s7fU/?spm_id_from333.999.0.0&vd_sourceecbdfcacb078d0…

五个高质量伤感视频素材资源站,帮你快速找到完美创作素材

在制作短视频、MV或者广告时&#xff0c;伤感主题的视频素材往往能触动观众的情感&#xff0c;让作品更具共鸣。无论是表达分手、离别&#xff0c;还是展现孤独与失落&#xff0c;合适的伤感素材对情感类创作至关重要。为帮助创作者找到优质的视频素材&#xff0c;以下推荐5个高…

天正建筑T20V8

链接: https://pan.baidu.com/s/1k-PcXJxHWPh3-6yAIfcaPg提取码: dvyn

JavaScript 实现文本转语音功能

全篇大概2000 字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间10分钟。 引言 我将向大家展示如何使用 JavaScript 和 Web Speech API 快速实现一个“文本转语音”的 Web 应用。通过这个教程&#xff0c;你将了解如何让浏览器将输入的文本朗读出来。 预览效果 一、…

DNS域名详细解析详解

文章目录 DNS域名详细解析详解一、引言二、DNS域名解析过程1、DNS解析概述1.1、DNS解析的基本步骤 2、代码示例 三、DNS查询类型1、递归查询2、迭代查询 四、总结 DNS域名详细解析详解 一、引言 在互联网的世界里&#xff0c;域名和IP地址是两个不可或缺的概念。IP地址是计算…

函数计算——文档与网页数据提取工具(MinerU)应用实践

1 引言 在信息爆炸的时代&#xff0c;AI研究者面临着从海量文档中提取高质量数据的挑战。随着大语言模型在各个领域的广泛应用&#xff0c;有效地处理和整合文档信息成为了基础性的任务。这些文档形式多样&#xff0c;包括学术文献、行业报告、会议PPT、课本、说明书及合同单据…

【网络】应用层——HTTP协议

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解什么是HTTP协议。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff1a;网络 &g…

计算生物学与生物信息学漫谈-5-mapping算法

之前的文章我们介绍了参考基因组&#xff0c;也介绍了一些基本概念&#xff0c;具体可以看之前的博客&#xff1a; 计算生物学与生物信息学漫谈-4-参考基因组与Mapping准备_基因组的map-CSDN博客 这次我们讲如何将read map到基因组上所用到的各种算法&#xff1a; 目录 1.1 …

qsqlmysql.lib的编译和使用

文章目录 打开源码 打开源码 打开qt源码安装路径 src相对路径下的文件Src\qtbase\src\plugins\sqldrivers\mysql 比如我是5.9.9版本我的路径就是&#xff1a;D:\Qt5.9.9\5.9.9\Src\qtbase\src\plugins\sqldrivers\mysql 可以看到待编译的mysql驱动文件 使用IDE打开pro文件进…

leetcode 693.交替位二进制数

1.题目要求&#xff1a; 2.题目代码: class Solution { public:bool hasAlternatingBits(int n) {int num n;//设置数组存入二进制位vector<int> array;while(num){array.push_back(num % 2); num num / 2;}//把数组颠倒就能得到此数真正二进制位reverse(array.begin…

IP协议知识点总结

IP协议主要分为三个 1. 地址管理 每个网络上的设备, 要能分配一个唯一的地址 2. 路由选择 小A 给小B 发消息, 具体应该走什么路线 3. 地址管理 IP 地址. 本质上是一个 32 位的整数 通常将, 32 位的整数使用点分十进制来表示, 如 192.168.1.1 一共可以表示 42 亿 9 千万个地址…

【重学 MySQL】八十二、深入探索 CASE 语句的应用

【重学 MySQL】八十二、深入探索 CASE 语句的应用 CASE语句的两种形式CASE语句的应用场景数据分类动态排序条件计算在 SELECT 子句中使用在 WHERE子句中使用在 ORDER BY 子句中使用 注意事项 在MySQL中&#xff0c;CASE 语句提供了一种强大的方式来实现条件分支逻辑&#xff0c…

机器学习1_机器学习定义——MOOC

一、机器学习定义 定义一 1959年Arthur Samuel提出机器学习的定义&#xff1a; Machine Learning is Fields of study that gives computers the ability to learn without being explicitly programmed. 译文&#xff1a;机器学习是这样的领域&#xff0c;它赋予计算机学习的…

充电桩--OCPP 充电通讯协议介绍

一、OCPP协议介绍 OCPP的全称是 Open Charge Point Protocol 即开放充电点协议&#xff0c; 它是免费开放的协议&#xff0c;该协议由位于荷兰的组织 OCA&#xff08;开放充电联盟&#xff09;进行制定。Open Charge Point Protocol (OCPP) 开放充电点协议用于充电站(CS)和任何…

如何制作公司小程序

我是【码云数智】平台的黄导&#xff0c;今天分享&#xff1a;如何制作公司小程序 企业小程序怎么制作&#xff0c;企业小程序制作不仅成为了连接消费者与品牌的桥梁&#xff0c;更是企业数字化转型的重要一环。 01、小程序制作流程 02、微信小程序开发多少钱 03、微信小程…

明道云正式发布国际品牌Nocoly

在2024年明道云伙伴大会上&#xff0c;明道云正式发布了其国际品牌Nocoly以及国际版产品Nocoly HAP。这标志着公司正式开启了海外业务。明道云的海外业务由全资拥有的Nocoly.com Limited经营&#xff0c;该公司注册在香港特别行政区。总部位于上海的明道云已经将围绕HAP超级应用…

如何构建一个可扩展的测试自动化框架?

以下为作者观点&#xff1a; 假设你是测试自动化方面的新手&#xff0c;想参与构建一个框架。在这种情况下&#xff0c;重要的是要了解框架所需的组件&#xff0c;以及它们是如何组合的。思考项目的具体需求和目标&#xff0c;以及可能遇到的困难和挑战。 假如你是一个测试架…

C++builder中的人工智能(11):双曲正切激活函数(ANN函数)?

在这篇文章中&#xff0c;我们将探讨双曲正切函数&#xff08;tanh&#xff09;是什么&#xff0c;以及如何在C中使用这个函数。让我们来回答这些问题。 在AI中激活函数意味着什么&#xff1f; 激活函数&#xff08;phi()&#xff09;&#xff0c;也称为转移函数或阈值函数&a…

基于SSM+VUE宠物医院后台管理系统JAVA|VUE|Springboot计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…