【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.mzph.cn/bicheng/59439.shtml

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

相关文章

Java项目实战II基于Java+Spring Boot+MySQL的网上摄影工作室(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着互联网…

宝塔Linux面板安装PHP扩展失败报wget: unable to resolve host address ‘download.bt.cn’

一、问题&#xff1a; 当使用宝塔面板安装PHP扩展失败出现如下错误时 Resolving download.bt.cn(download.bt.cn)...failed: Connection timed out. wget: unable toresolve host address download.bt.cn’ 二、解决&#xff1a; 第一步&#xff1a;如下命令执行拿到返回的I…

攻防靶场(28):通过SNMP进行信息收集 JOY

目录 1.侦查 1.1 获取目标网络信息&#xff1a;IP地址 1.2 主动扫描&#xff1a;扫描IP地址块 1.3 收集受害者主机信息&#xff1a;软件 2. 数据窃取 2.1 通过备用协议窃取&#xff1a;通过未加密的非C2协议窃取 2.2 通过备用协议窃取&#xff1a;通过未加密的非C2协议窃取 3. …

LeetCode:83. 删除排序链表中的重复元素 II(java) 保留一个重复的

目录 题目描述: 代码: 第一种: 第二种: 题目描述: 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2]示例 2&#xff1a…

C++ 类与对象(类、实例化、this指针)

目录 一、前言 二、正文 1.类和对象 1.1类定义格式 1.2访问限定符 1.3类域 2.实例化 2.1实例化概念 2.2对象大小 3.this指针 三、结语 一、前言 前面我们已经了解了缺省参数和函数重载等&#xff0c;今天让我们来学习类与对象吧&#xff0c;因为类与对象的内容很多&a…

【jvm】为什么Xms和Xmx的值通常设置为相同的?

目录 1. 说明2. 避免性能开销3. 提升稳定性4. 简化配置5. 优化垃圾收集6. 获取参数6.1 代码示例6.2 结果示例 1. 说明 1.-Xms 和 -Xmx 参数分别用于设置堆内存的初始大小&#xff08;最小值&#xff09;和最大大小。2.在开发环境中&#xff0c;开发人员可能希望快速启动应用程…

大模型面试题:Prefix LM和Causal LM的区别是啥?

获取更多面试真题的集合&#xff0c;请移步至 https://pica.zhimg.com/80/v2-7fd6e77f69aa02c34ca8c334870b3bcd_720w.webp?sourced16d100b 说白了&#xff1a;就是attention mask不一样&#xff0c;如下所示&#xff1a; img 它们定义如下&#xff1a; Prefix LM&#xff…

学习虚幻C++开发日志——定时器

官方文档&#xff1a;虚幻引擎中的Gameplay定时器 | 虚幻引擎 5.5 文档 | Epic Developer Community | Epic Developer Community 定时器 安排在经过一定延迟或一段时间结束后要执行的操作。例如&#xff0c;您可能希望玩家在获取某个能力提升道具后变得无懈可击&#xff0c;…

Windows达梦8数据库:本地编码:PG_GBK, 导入文件编码:PG_UTF8错误最优解决方法

在windows使用达梦8DM管理工具直接导入.dmp文件(可能是从Linux导出的)时出现该错误 错误如下 解决方案如下&#xff1a; 1、重新建立UTF-8编码的数据库 2、新建一个模式 3、使用CMD 命令进行导入 找到DM数据库的安装路径的bin 目录下 cmd 进入终端&#xff0c;使用命令&…

【极客兔兔-Web框架Gee详解】Day0 序言

文章目录 一、Web 开发1. 什么是Web 开发&#xff1f;2. 主要组成部分2.1 前端开发2.2 后端开发2.3 全栈开发2.4 数据库管理 3. Web开发过程3.1 规划和设计&#xff1a;3.2 开发和编码&#xff1a;3.3 测试和优化&#xff1a;3.4 部署和维护&#xff1a; 4. 总结 二、用标准库n…

树叶分类竞赛(Baseline)以及kaggle的GPU使用

树叶分类竞赛(Baseline)-kaggle的GPU使用 文章目录 树叶分类竞赛(Baseline)-kaggle的GPU使用竞赛的步骤代码实现创建自定义dataset定义data_loader模型定义超参数训练模型预测和保存结果 kaggle使用 竞赛的步骤 本文来自于Neko Kiku提供的Baseline&#xff0c;感谢大佬提供代码…

服务器作业(2)

架设一台NFS服务器&#xff0c;并按照以下要求配置 关闭防火墙 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 配置文件设置&#xff1a; [rootlocalhost ~]# vim /etc/exports 1、开放/nfs/shared目录&#xff0c;供所有用户查询资料 共享…

014:无人机遥控器操作

摘要&#xff1a;本文详细介绍了无人机遥控器及其相关操作。首先&#xff0c;解释了油门、升降舵、方向舵和副翼的概念、功能及操作方式&#xff0c;这些是控制无人机飞行姿态的关键部件。其次&#xff0c;介绍了美国手、日本手和中国手三种不同的操作模式&#xff0c;阐述了遥…

Redis的持久化以及性能管理

目录 一、Redis持久化概述 1.什么是Redis持久化 2.持久化方式 3.RDB持久化 3.1概念 3.2触发条件 3.3执行流程 3.4启动时加载 4. AOF持久化 4.1概念 4.2启动AOF 4.3执行流程 4.4启动时加载 5.RDB和AOF的优缺点 二、Redis性能管理 1.查看Redis内存使用 2…

Java项目实战II基于Java+Spring Boot+MySQL的高校办公室行政事务管理系统(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在高等教育…

HTB:PermX[WriteUP]

目录 连接至HTB服务器并启动靶机 1.How many TCP ports are listening on PermX? 使用nmap对靶机TCP端口进行开放扫描 2.What is the default domain name used by the web server on the box? 使用curl访问靶机80端口 3.On what subdomain of permx.htb is there an o…

在Vue和OpenLayers中使用移动传感器实现飞机航线飞行模拟

项目实现的核心代码 项目概述 该项目的目标是使用Vue.js作为前端框架&#xff0c;结合OpenLayers用于地图显示&#xff0c;实时获取来自手机传感器的数据&#xff08;如经纬度、高度、速度&#xff09;来模拟飞机在地图上的飞行轨迹。整体架构如下&#xff1a; Vue.js 用于构建…

数据库基础(2) . 安装MySQL

0.增加右键菜单选项 添加 管理员cmd 到鼠标右键 运行 reg文件 在注册表中添加信息 这样在右键菜单中就有以管理员身份打开命令行的选项了 1.获取安装程序 网址: https://dev.mysql.com/downloads/mysql/ 到官网下载MySQL8 的zip包, 然后解压 下载后的包为: mysql-8.0.16-…

文心一言 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 给出&…

深度学习基础知识-损失函数

目录 1. 均方误差&#xff08;Mean Squared Error, MSE&#xff09; 2. 平均绝对误差&#xff08;Mean Absolute Error, MAE&#xff09; 3. Huber 损失 4. 交叉熵损失&#xff08;Cross-Entropy Loss&#xff09; 5. KL 散度&#xff08;Kullback-Leibler Divergence&…