当前位置: 首页 > news >正文

自定义Shell命令行解释器

目录

1、目标

2、显示命令提示符

2.1 getenv

2.2 getcwd

2.3 putenv

3、获取用户输入的命令

4、解析命令

5、处理内建命令

6、处理外部命令

7、完整代码

7.1 myshell.cpp

7.2 Makefile


1、目标

实现一个Linuxmyshell,有以下基本的功能。

  1. 显示命令提示符
  2. 获取用户输入的命令
  3. 解析命令
  4. 处理内建命令
  5. 处理外部命令

myshell有一张命令行参数表环境变量表(继承bash的,其实应该要从配置文件中获取,但比较麻烦)。

2、显示命令提示符

这里就要了解一下:getenv(),getcwd(),putenv()了。

2.1 getenv

获取环境变量

char *getenv(const char *name);
  • 根据环境变量名(如 "PATH")返回其对应的值(字符串)。

  • 如果变量不存在返回 NULL

2.2 getcwd

获取当前工作路径

char *getcwd(char *buf, size_t size);
  • 当前工作目录的绝对路径写入 buf,并返回 buf

  • 需确保 buf 足够大(否则返回 NULL,errno = ERANGE)。

注意:

进程的环境变量表中的PWD,是根据进程的CWD(进程当前的工作路径,在/proc/pid/下可以看到),进行更新的。 

2.3 putenv

设置环境变量

int putenv(char *string);
  • 设置环境变量,格式"KEY=VALUE" 的字符串。如果已存在相同的KEY,就覆盖VALUE

  • 成功返回 0失败返回非零

注意:

putenv()传的是指针要放在环境变量表里生命周期要和程序一样长,所以传全局变量

改变这个全局变量环境变量表中也会改变(当时不太理解,中坑了。)

const char* getUserName()
{const char* USER = getenv("USER");return USER == NULL?"None":USER;
}const char* getHostName()
{const char* HOSTNAME = getenv("HOSTNAME");return HOSTNAME == NULL?"None":HOSTNAME;
}std::string getCwd()
{char cwdenv[1024];char* cwd = getcwd(cwdenv,sizeof(cwdenv));return cwd == NULL? "None":cwdenv; // 因为返回的是局部变量,所以使用string,进行拷贝
}char pwd[1024];
void cmdPrompt()
{std::string path = getCwd();// 更新环境变量PWD,不能putenv局部变量。snprintf(pwd,sizeof(pwd),"PWD=%s",path.c_str());putenv(pwd);if(path.size() != 1) // 不是/根目录{int index = path.rfind('/');path = path.substr(index+1);}std::cout<<"["<<getUserName()<<"@"<<getHostName()<<" "<<path<<"]"<<"$$";
}

3、获取用户输入的命令

bool getCmd(char cmd[],int size)
{char* s = fgets(cmd,size,stdin);if(s != NULL){int len = strlen(cmd);cmd[len-1] = '\0'; // fgets会读取'\n',需要处理}return s != NULL;
}

获取成功,返回true,获取失败,返回false。 

4、解析命令

bool parseCmd(char cmd[],char* argv[])
{const char* delim = " ";char* token = strtok(cmd,delim);int i = 0;while(token != NULL){argv[i] = token;token = strtok(NULL,delim);++i;}argv[i] = NULL; // 进程替换,要求以NULL结尾return i != 0;
}
char *strtok(char *str, const char *delim);
  • str:要分割的字符串。第一次调用时传入原始字符串后续调用传入 NULL

  • delim:包含所有可能分隔符的字符串。

  • 返回分割出的子字符串的指针。如果没有更多子字符串,则返回 NULL

  • strtok 会在找到的分隔符位置插入 '\0' 字符,因此会修改原始字符串。

5、处理内建命令

内建命令需要父进程自己执行(如:cd,需要改变自己的路径),或父进程自己执行效率更高(如:echo)。 

std::string getCwd()
{char cwdenv[1024];char* cwd = getcwd(cwdenv,sizeof(cwdenv));return cwd == NULL? "None":cwdenv; // 因为返回的是局部变量,所以使用string,进行拷贝
}const char* getHome()
{const char* HOME = getenv("HOME");return HOME == NULL?"None":HOME;
}const char* getOldPwd()
{const char* OLDPWD = getenv("OLDPWD");return OLDPWD == NULL?"None":OLDPWD;
}char oldPwd[1024];
void cd(char* argv[])
{ std::string cwdenv = getCwd();if(argv[1] == NULL || strcmp(argv[1],"~") == 0){snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd); // 更新OLDPWDchdir(getHome());}else if(strcmp(argv[1],"-") == 0){chdir(getOldPwd());snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd);}else{snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd); // 更新OLDPWDchdir(argv[1]);}
}int lastExitno = 0;
void echo(char* argv[])
{if(argv[1] == NULL)return;std::string cmd = argv[1];// echo $?// echo $PATH// echo "hello world"if(cmd[0] == '$'){if(cmd.substr(1) == "?"){std::cout<<lastExitno<<std::endl;lastExitno = 0;}else{if(getenv(cmd.substr(1).c_str()))std::cout<<getenv(cmd.substr(1).c_str())<<std::endl;}}else{std::cout<<cmd<<std::endl;}
}bool executeBuiltIn(char* argv[])
{std::string cmd = argv[0];if(cmd == "cd"){cd(argv);return true;}else if(cmd == "echo"){echo(argv);return true;}// ...else{}return false;
}

注意:

    else if(strcmp(argv[1],"-") == 0){chdir(getOldPwd());snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd);}

如果之前putenv(oldPwd),传的是指针,

若先snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());

那么,环境变量表中的OLDPWD就已经是CWD了,那么chdir(getOldPwd());就出错了。

6、处理外部命令

外部命令,防止父进程挂了,所以创建子进程,进行程序替换(可执行任意程序)。 

int executeExternal(char* argv[])
{pid_t id = fork();if(id == -1)return 1;if(id == 0){execvp(argv[0],argv);exit(2);}int status = 0;pid_t wid = waitpid(id,&status,0);if(wid == id && WIFEXITED(status)) // 子进程退出,且是正常退出lastExitno = WEXITSTATUS(status);return 0;
}

7、完整代码

7.1 myshell.cpp

#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>const char* getUserName()
{const char* USER = getenv("USER");return USER == NULL?"None":USER;
}const char* getHostName()
{const char* HOSTNAME = getenv("HOSTNAME");return HOSTNAME == NULL?"None":HOSTNAME;
}const char* getHome()
{const char* HOME = getenv("HOME");return HOME == NULL?"None":HOME;
}std::string getCwd()
{char cwdenv[1024];char* cwd = getcwd(cwdenv,sizeof(cwdenv));return cwd == NULL? "None":cwdenv; // 因为返回的是局部变量,所以使用string,进行拷贝
}const char* getOldPwd()
{const char* OLDPWD = getenv("OLDPWD");return OLDPWD == NULL?"None":OLDPWD;
}char pwd[1024];
void cmdPrompt()
{std::string path = getCwd();// 更新环境变量PWD,不能putenv局部变量。snprintf(pwd,sizeof(pwd),"PWD=%s",path.c_str());putenv(pwd);if(path.size() != 1) // 不是/根目录{int index = path.rfind('/');path = path.substr(index+1);}std::cout<<"["<<getUserName()<<"@"<<getHostName()<<" "<<path<<"]"<<"$$";
}bool getCmd(char cmd[],int size)
{char* s = fgets(cmd,size,stdin);if(s != NULL){int len = strlen(cmd);cmd[len-1] = '\0'; // fgets会读取'\n',需要处理}return s != NULL;
}bool parseCmd(char cmd[],char* argv[])
{const char* delim = " ";char* token = strtok(cmd,delim);int i = 0;while(token != NULL){argv[i] = token;token = strtok(NULL,delim);++i;}argv[i] = NULL; // 进程替换,要求以NULL结尾return i != 0;
}char oldPwd[1024];
void cd(char* argv[])
{ std::string cwdenv = getCwd();if(argv[1] == NULL || strcmp(argv[1],"~") == 0){snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd); // 更新OLDPWDchdir(getHome());}else if(strcmp(argv[1],"-") == 0){chdir(getOldPwd());snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd);}else{snprintf(oldPwd,sizeof(oldPwd),"OLDPWD=%s",cwdenv.c_str());putenv(oldPwd); // 更新OLDPWDchdir(argv[1]);}
}int lastExitno = 0;
void echo(char* argv[])
{if(argv[1] == NULL)return;std::string cmd = argv[1];// echo $?// echo $PATH// echo "hello world"if(cmd[0] == '$'){if(cmd.substr(1) == "?"){std::cout<<lastExitno<<std::endl;lastExitno = 0;}else{if(getenv(cmd.substr(1).c_str()))std::cout<<getenv(cmd.substr(1).c_str())<<std::endl;}}else{std::cout<<cmd<<std::endl;}
}bool executeBuiltIn(char* argv[])
{std::string cmd = argv[0];if(cmd == "cd"){cd(argv);return true;}else if(cmd == "echo"){echo(argv);return true;}// ...else{}return false;
}int executeExternal(char* argv[])
{pid_t id = fork();if(id == -1)return 1;if(id == 0){execvp(argv[0],argv);exit(2);}int status = 0;pid_t wid = waitpid(id,&status,0);if(wid == id && WIFEXITED(status)) // 子进程退出,且是正常退出lastExitno = WEXITSTATUS(status);return 0;
}int main()
{while(true){// 命令提示符cmdPrompt();// 获取用户输入命令char cmd[1024] = {0};if(!getCmd(cmd,sizeof(cmd)))continue;// 解析命令char* argv[1024] = {0};if(!parseCmd(cmd,argv))continue;// 内建命令if(executeBuiltIn(argv))continue;// 执行命令executeExternal(argv);}return 0;
}

7.2 Makefile

TARGET := myshell
SRCS := myshell.cpp
SUFFIX := .cpp
OBJS := $(SRCS:$(SUFFIX)=.o)
CC := g++$(TARGET): $(OBJS)$(CC) -o $@ $^%.o: %$(SUFFIX)$(CC) -c $<.PHONY: clean
clean:rm -f $(OBJS) $(TARGET)
http://www.lqws.cn/news/72505.html

相关文章:

  • 数据结构哈希表总结
  • [SC]SystemC中常用的宏和小工具
  • 抛砖引玉:RadarDet4D,NuScenes数据集Radar模态目标检测第二名(即将开源)
  • uniapp-商城-77-shop(8.2-商品列表,地址信息添加,级联选择器picker)
  • 3. TypeScript 中的数据类型
  • Linux磁盘管理
  • 业务到解决方案构想
  • SQL 中的 `CASE WHEN` 如何使用?
  • 达梦数据库 Windows 系统安装教程
  • CentOS8.3+Kubernetes1.32.5+Docker28.2.2高可用集群二进制部署
  • 状态机实现文件单词统计
  • 人工智能在智能制造业中的创新应用与未来趋势
  • HealthBench医疗AI评估基准:技术路径与核心价值深度分析(上)
  • 架构师面试题整理
  • VitalInsight智能体检报告解读
  • 【深度学习-Day 21】框架入门:神经网络模型构建核心指南 (Keras PyTorch)
  • 每天总结一个html标签——a标签
  • CMake指令:string(字符串操作)
  • Linux--进程概念
  • 车载诊断架构 --- DTC消抖参数(Trip Counter DTCConfirmLimit )
  • 05-power BI高级筛选器filter与Values人工造表
  • NVM,Node.Js 管理工具
  • NodeJS全栈WEB3面试题——P4Node.js后端集成 服务端设计
  • neo4j 5.19.0两种基于向量进行相似度查询的方式
  • Node.js 中使用 Express 框架系统详细讲解
  • Redis持久化机制详解:RDB与AOF的深度剖析
  • 风控研发大数据学习路线
  • 大数据学习(127)-hive日期函数
  • 使用Python进行函数作画
  • 超声波测距三大算法实测对比