【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.rhkb.cn/news/464687.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git 概述及相关命令(1)

Git概述 Git是一个强大的分布式版本控制系统&#xff0c;广泛用于代码管理和协作开发。 仓库&#xff08;Repository&#xff09;: 存储项目文件及其历史记录的地方&#xff0c;分为本地仓库和远程仓库。工作区&#xff08;Working Directory&#xff09;: 用户当前工作文件所…

Java栈和队列的快速入门

栈和队列 一、栈 Stack1、概念2、基本操作3、常用方法4、举例5、分析 二、队列1、概念2、常用方法3、举例4、分析&#xff1a; 三、力扣算法快速入门232. 用栈实现队列225. 用队列实现栈 感谢 一、栈 Stack 1、概念 在 Java 中&#xff0c;栈&#xff08;Stack&#xff09;是…

docker 可用镜像服务地址(2024.10.31亲测可用)

1.错误 Error response from daemon: Get “https://registry-1.docker.io/v2/” 原因&#xff1a;镜像服务器地址不可用。 2.可用地址 编辑daemon.json&#xff1a; vi /etc/docker/daemon.json内容修改如下&#xff1a; {"registry-mirrors": ["https://…

【MySQL】深层理解索引及特性(重点)--下(12)

索引&#xff08;重点&#xff09; 1. 索引的作用2. 索引操作2.1 主键索引2.1.1 主键索引的特点2.1.2 创建主键索引 2.2 唯一键索引2.2.1 唯一键索引的特点2.2.2 唯一索引的创建 2.3 普通索引2.3.1 普通索引的特点2.3.2 普通索引的创建 2.4 全文索引2.4.1 全文索引的作用2.4.2 …

临街矩阵乘以自己转置的含义

总结: 临街矩阵* 邻接矩阵转置的(i,j) 位置表示有多少种线路从元素A跳转一条边最终落到元素j的路线. 这个也叫1_degree.

A010-基于SpringBoot的宠物健康咨询系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

DP3复现基础知识(一)—— Hydra 库

DP3 无论是 train 还是 eval 均使用了 Hydra 这一个python 库&#xff0c;这就有些代码在看的时候难以理解其通讯逻辑&#xff0c;例如&#xff1a; hydra.main(version_baseNone,config_pathstr(pathlib.Path(__file__).parent.joinpath(diffusion_policy_3d, config)) ) Hy…

记单词,不要迷信一种方法

记单词&#xff0c;不要迷信一种方法。因为&#xff0c;记单词的目的&#xff0c;就是记住单词呀。 哪一种方法能让你记住&#xff0c;快速、高效、长久地记住&#xff0c;你就使用哪种方法&#xff1b;而且&#xff0c;方法和方法之间&#xff0c;不见得是矛盾的呀。 我们举个…

【自动化利器】12个评估大语言模型(LLM)质量的自动化框架

LLM评估是指在人工智能系统中评估和改进语言和语言模型的过程。在人工智能领域&#xff0c;特别是在自然语言处理&#xff08;NLP&#xff09;及相关领域&#xff0c;LLM评估具有至高无上的地位。通过评估语言生成和理解模型&#xff0c;LLM评估有助于细化人工智能驱动的语言相…

IO流篇(一、File)

目录 一、学习前言 二、文件简介 三、文件使用 1. 绝对路径 vs 相对路径 2. 路径分隔符 3. 属性&#xff08;字段&#xff09; 4. 构造方法 5. 常用方法 5.1. 获取文件的相关信息 5.2. 判断功能 5.3. 新建和删除 5.4. 文件的获取 5.5. 重命名文件 四、文件使用练习…

spring ai 入门 之 结构化输出 - 把大模型llm返回的内容转换成java bean

目录 ​编辑 将AI非结构化文本转换为特定格式数据的应用场景说明 Spring AI 介绍 &#xff1a;为Java开发者打造的AI应用开发框架 Qwen 介绍 &#xff1a; 一个国内领先的开源大模型 Spring AI Alibaba框架介绍 &#xff1a; 一个国内最好的spring ai实现 使用spring ai …

文心一言 VS 讯飞星火 VS chatgpt (383)-- 算法导论24.5 3题

三、对引理 24.10 的证明进行改善&#xff0c;使其可以处理最短路径权重为 ∞ ∞ ∞ 和 − ∞ -∞ −∞ 的情况。引理 24.10(三角不等式)的内容是&#xff1a;设 G ( V , E ) G(V,E) G(V,E) 为一个带权重的有向图&#xff0c;其权重函数由 w : E → R w:E→R w:E→R 给出&…

漫途焊机安全生产监管方案,提升安全生产管理水平!

随着智能制造时代的到来&#xff0c;企业安全生产管理的重要性日益凸显。特别是在现代工厂中&#xff0c;焊机的安全生产监管成为了一个不容忽视的重要环节。传统的焊机安全生产监管方式存在诸多不足&#xff0c;如人工巡检频率低、数据延迟、安全隐患发现不及时等问题。因此&a…

csp2024T3

题目大意&#xff1a;对于每个数而言&#xff0c;可以将其染成红或蓝&#xff0c;对于每一个数&#xff0c;定义其贡献为&#xff0c;当且仅当这个数最近的同色数与其相等&#xff0c;否则其贡献为0&#xff0c;求最大贡献和。 思路&#xff1a;考虑dp 1.考场20多分钟想的奇怪…

十六届蓝桥杯嵌入式资料 看这个就够了(附CSDN开源程序)

蓝桥杯嵌入式终极模板&#xff0c;简单配置&#xff0c;功能全面 一小时玩转蓝桥杯嵌入式开发版 除按键和 LED 其余模块都来自官方选手资料包 代码简洁工整&#xff0c;参数&#xff0c;函数体分模块&#xff0c;有非常详细的注释&#xff0c;初始化由 cubemx 生成 &#xff08…

【测试工具】Fastbot 客户端稳定性测试

背景 做这个主要为了发版之前提前发现崩溃&#xff0c;风险前置。适合客户端很重的业务。 优点&#xff1a;你不改动也能用&#xff0c; 维护成本不高。 缺点&#xff1a;容易进入H5页面无法返回&#xff0c;效果有限。 备注&#xff1a;我这边接手别人维护&#xff0c;公司…

苍穹外卖Bug集合

初始化后端项目运行出现以下问题 以上报错是因为maven和jdk版本不符合&#xff0c;需要将jdk改成17&#xff0c;mavne改成3.9.9

中国雕塑、

孙溟㠭浅析“印章” 印章又称“图章”&#xff0c;玺印起源商代&#xff0c;至少在春秋战国时已出现&#xff0c;因战国时代已普遍使用。 商玺 古玺是先秦印章的通称&#xff0c;秦始皇统一六国之后&#xff0c;皇帝用印称“璽&#xff08;玺&#xff09;”&…

Android App 技能在DuerOS的调试方法

温故知新&#xff0c;我们先回顾一下DuerOS的技能分类。根据不同的视角可以对DuerOS 目前支持的技能类型进行不同的分类&#xff0c;例如&#xff0c;从用户与技能的语音交互方式来看&#xff0c; 可以将技能分为这四种技能类型: L1技能&#xff1a;只支持语音的打开和关闭L2技…

Ghidra无头模式(自动化批处理执行重复性任务)

Ghidra无头模式&#xff08;自动化批处理执行重复性任务&#xff09; 与Ghidra GUI探索单个项目中的单个文件不同&#xff0c;Ghidra headless analyzer&#xff08;Ghidra无头分析器&#xff09;更加适合批处理和用脚本控制Ghidra。 &#xff08;一&#xff09;启动analyzeHea…