【Linux】简易版shell
文章目录
- shell的基本框架
- PrintCommandLine
- GetCommandLine
- ParseCommandLine
- ExecuteCommand
- InitEnv
- CheckAndExecBuildCommand
- 代码总览
- 运行效果
- 总结
shell的基本框架
要写一个命令行我们首先要写出基本框架。
- 打印命令行
- 获取用户输入的命令
- 分析命令
- 执行命令
基本框架的代码:
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更加功能丰富。
相关文章:

【Linux】简易版shell
文章目录 shell的基本框架PrintCommandLineGetCommandLineParseCommandLineExecuteCommandInitEnvCheckAndExecBuildCommand代码总览运行效果总结 shell的基本框架 要写一个命令行我们首先要写出基本框架。 打印命令行获取用户输入的命令分析命令执行命令 基本框架的代码&am…...

宝塔Linux面板安装PHP扩展失败报wget: unable to resolve host address ‘download.bt.cn’
一、问题: 当使用宝塔面板安装PHP扩展失败出现如下错误时 Resolving download.bt.cn(download.bt.cn)...failed: Connection timed out. wget: unable toresolve host address download.bt.cn’ 二、解决: 第一步:如下命令执行拿到返回的I…...
问:Redis常见性能问题及解法?
Redis 作为一个高性能的键值存储系统,在实际应用中可能会遇到各种性能问题。本文将探讨 Redis 的常见性能问题,并提供相应的解决建议。主要针对五个关键问题进行讨论:Master 节点的持久化工作、Slave 节点的数据备份、主从复制的网络环境、主…...

Imperva 数据库与安全解决方案
Imperva是网络安全解决方案的专业提供商,能够在云端和本地对业务关键数据和应用程序提供保护。公司成立于 2002 年,拥有稳定的发展和成功历史并于 2014 年实现产值1.64亿美元,公司的3700多位客户及300个合作伙伴分布于全球各地的90多个国家。…...

【JavaScript】之文档对象模型(DOM)详解
JavaScript 的强大之处在于它能够与 HTML 和 CSS 交互,动态地修改网页内容和样式。而实现这一功能的核心就是 DOM(文档对象模型)。 一、什么是 DOM? DOM 是文档对象模型(Document Object Model)的缩写。它…...
速盾:cdn域名与ip区别
CDN(内容分发网络)是一种通过在全球多个服务器上缓存和分发静态资源的网络服务,可以提高网站的访问速度和性能。在使用CDN时,域名与IP地址是两个关键的概念。本文将介绍CDN域名与IP地址的区别和作用。 首先,CDN域名是…...

如何优雅的在页面上嵌入AI-Agent人工智能
前言 IDEA启动!大模型的title想必不用我多说了,多少公司想要搭上时代前言技术的快车,感受科技的魅力。现在大模型作为降本增效的强大工具,基本上公司大多人都想要部署开发一把,更多的想要利用到这些模型放到生产中来提…...

如何对LabVIEW软件进行性能评估?
对LabVIEW软件进行性能评估,可以从以下几个方面着手,通过定量与定性分析,全面了解软件在实际应用中的表现。这些评估方法适用于确保LabVIEW程序的运行效率、稳定性和可维护性。 一、响应时间和执行效率 时间戳测量:使用LabVIEW的时…...

动态规划 —— dp问题-按摩师
1. 按摩师 题目链接: 面试题 17.16. 按摩师 - 力扣(LeetCode)https://leetcode.cn/problems/the-masseuse-lcci/description/ 2. 算法原理 状态表示:以某一个位置为结尾或者以某一个位置为起点 dp[i]表示:选择到i位置…...
SQL 语法学习
在当今数字化的时代,数据的管理和分析变得至关重要。而 SQL(Structured Query Language),即结构化查询语言,作为一种用于管理关系型数据库的强大工具,掌握它对于从事数据相关工作的人来说是一项必备技能。在…...

MYSQL---TEST5(Trigger触发器Procedure存储过程综合练习)
触发器Trigger 数据库mydb16_trigger创建 表的创建 goods create table goods( gid char(8) primary key, #商品号 name varchar(10), #商品名 price decimal(8,2), #价格 num int;) #数量orders create tabl…...

蓝桥杯 区间移位--二分、枚举
题目 代码 #include <stdio.h> #include <string.h> #include <vector> #include <algorithm> #include <iostream> using namespace std; struct node{ int a,b; }; vector<node> q; bool cmp(node x,node y){ return x.b <…...
Nginx 报错400 Request Header Or Cookie Too Large
错误的原因: 1、可能是你的网络DNS配置错误。 2、由request header过大所引起,request过大,通常是由于cookie中写入了较大的值所引起的。 3、访问太频繁,浏览器的缓存量太大,产生错误。 解决办法: 1、清…...
【Redis】一种常见的Redis分布式锁原理简述
本文主要简述一下基于set命令的Redis分布式锁的原理。 一,a线程持有的锁不要被b线程同时持有→setnx 抢锁的时候,最核心的就是,a线程持有的锁不要被b线程同时持有,放在基于set命令的redis分布式锁中来看,就是“如果锁…...

HOT100_最大子数组和
class Solution {public int maxSubArray(int[] nums) {int[] dp new int[nums.length];int res nums[0];dp[0] nums[0];for(int i 1; i< nums.length; i){dp[i] Math.max(nums[i] ,dp[i-1] nums[i]);res Math.max(res, dp[i]);}return res;} }...
DiskGenius工具扩容Mac OS X Apple APFS分区
DiskGenius是一款功能强大的磁盘分区工具,它支持Windows和Mac OS X系统,可以用于管理硬盘分区,包括扩容Mac OS X的Apple APFS分区。然而,直接使用DiskGenius来扩容Mac OS X的APFS分区可能存在一定的风险,因为不是专门为…...
从零开始的LeetCode刷题日记:70. 爬楼梯
一.相关链接 题目链接:70. 爬楼梯 二.心得体会 这道题还是动规五部曲。 1.首先是dp数组及其下标的含义,dp记录了每层楼梯对应的爬的方法,每个下标存储每个对应楼层。 2.然后是递归公式,其实每一层楼都是可以从下面一层和下面…...

Unity照片墙效果
Unity照片墙效果,如下效果展示 。 工程源码...

【自动化利器】12个评估大语言模型(LLM)质量的自动化框架
LLM评估是指在人工智能系统中评估和改进语言和语言模型的过程。在人工智能领域,特别是在自然语言处理(NLP)及相关领域,LLM评估具有至高无上的地位。通过评估语言生成和理解模型,LLM评估有助于细化人工智能驱动的语言相…...
【1】基础概念
文章目录 一、特点二、基础语法注意三、官方编程指南四、go 语言标准库 API 一、特点 golang 一个 go 文件都要归属到一个包,需要进行申明。天然的并发:golang 从语言层面支持大并发。每个 go 文件都必须要归属到一个包中。执行 go 文件:go …...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...