【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 …...

HTML 文档规范与解析模式:DOCTYPE、<html> 标签以及结构化页面
文章目录 `<!DOCTYPE html>` 文档类型声明标准模式与怪异模式HTML5 的简化声明`<html>` 标签`<head>` 标签`<body>` 标签小结<!DOCTYPE html> 文档类型声明 在 HTML 文档中,<!DOCTYPE html> 是一个重要的文档类型声明,主要用于告知浏览…...

大模型微调技术 --> 脉络
Step1:脉络 微调技术从最早期的全模型微调演变成如今的各种参数高效微调(PEFT)方法,背后是为了应对大模型中的计算、存储和数据适应性的挑战 1.为什么有微调? 深度学习模型越来越大,尤其是 NLP 中的预训练语言模型(BERT, GPT)系列。如果从…...

不要只知道deepl翻译,这里有10个专业好用的翻译工具等着你。
deepl翻译的优点还是有很多的,比如翻译的准确性很高,支持翻译的语言有很多,并且支持翻译文件和文本。但是现在翻译工具那么多,大家需要翻译的场景也有很多,怎么能只拥有一个翻译工具呢。所以在这里我帮助大家寻找了一波…...

第二节 管道符、重定向与环境变量
1.重定向技术的 5 种模式 (1)标准覆盖输出重定向 (2)标准追加输出重定向 (3)错误覆盖输出重定向 (4)错误追加输出重定向 (5)输入重定向2.输入输出重定向 输入…...

Linux 服务器使用指南:从入门到登录
🌟快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。 🌟 🚩博主致力于用通俗易懂且不失专业性的文字,讲解计算机领域那些看似枯燥的知识点🚩 目录 一…...

QT 如何使QLabel的文字垂直显示
想要实现QLabel文字的垂直显示,可以通过使用“文字分割填充换行符”的方式来实现QLabel文字垂直显示的效果,下面是效果图: 具体实现代码: #include "mainwindow.h" #include "ui_mainwindow.h"MainWindow:…...

蓬勃发展:移动开发——关于软件开发你需要知道些什么
一、前言 移动开发一直都是软件开发领域中最有趣的领域之一,这是因为: 1、移动开发为“只有一个人”的开发团队提供了一个非常独特的机会,让他可以在相对较短的时间内建立一个实际的、可用的、有意义的应用程序; 2、移动开发也代…...

1095. 山脉数组中查找目标值
目录 题目解法lambda在这是怎么用的? 题目 (这是一个 交互式问题 ) 你可以将一个数组 arr 称为 山脉数组 当且仅当: arr.length > 3 存在一些 0 < i < arr.length - 1 的 i 使得: arr[0] < arr[1] <…...

【深度学习】InstantIR:图片高清化修复
InstantIR——借助即时生成参考的盲图像修复新方法 作者:Jen-Yuan Huang 等 近年来,随着深度学习和计算机视觉技术的飞速发展,图像修复技术取得了令人瞩目的进步。然而,对于未知或复杂退化的图像进行修复,仍然是一个充满挑战的任务。针对这一难题,研究者们提出了 Insta…...

推荐一款PowerPoint转Flash工具:iSpring Suite
iSpring Suite是一款PowerPoint转Flash工具,使用iSpring Suite 8可以轻松的将PPT演示文档转换为对Web友好的Flash影片格式。软件界面简洁,使用方便。为什么要转换成flash格式呢?Flash格式的最大特点是体积小巧、易于分发,兼容所有的操作系统…...