自定义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.pswp.cn/bicheng/83403.shtml

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

相关文章

Laplace 噪声

Laplace 噪声是一种特定概率分布&#xff08;拉普拉斯分布&#xff09;产生的随机扰动。它是差分隐私&#xff08;Differential Privacy, DP&#xff09;中最核心、最常用的噪声机制之一。它的核心作用是在不泄露个体信息的前提下&#xff0c;允许从包含敏感数据的数据库中提取…

基于空天地一体化网络的通信系统matlab性能分析

目录 1.引言 2.算法仿真效果演示 3.数据集格式或算法参数简介 4.MATLAB核心程序 5.算法涉及理论知识概要 5.1 QPSK调制原理 5.2 空天地一体化网络信道模型 5.3 空天地一体化网络信道特性 6.参考文献 7.完整算法代码文件获得 1.引言 空天地一体化网络是一种将卫星通信…

【Delphi】接收windows文件夹中文件拖拽

本文根据EmailX45的视频文件&#xff0c;进行了优化改进&#xff0c;原文参见&#xff1a;Delphi: Drag and Drop Files from Explorer into TPanel / TMemo - YouTube 在Windows中&#xff0c;如果将选择的文件拖动到Delphi程序的控件上&#xff0c;有很多实现方法&#xff0c…

基于热力学熵增原理的EM-GAN

简介 简介:提出基于热力学熵增原理的EM-GAN,通过生成器熵最大化约束增强输出多样性。引入熵敏感激活函数与特征空间熵计算模块,在MNIST/CelebA等数据集上实现FID分数提升23.6%,有效缓解模式崩溃问题。 论文题目:Entropy-Maximized Generative Adversarial Network (EM-G…

HashMap与ConcurrentHashMap详解:实现原理、源码分析与最佳实践

引言 在Java编程中&#xff0c;集合框架是最常用的工具之一&#xff0c;而HashMap和ConcurrentHashMap则是其中使用频率最高的两个Map实现。它们都用于存储键值对数据&#xff0c;但在实现机制、性能特点和适用场景上有着显著差异。 HashMap作为单线程环境下的首选Map实现&am…

CSS之动画(奔跑的熊、两面反转盒子、3D导航栏、旋转木马)

一、 2D转换 1.1 transform: translate( ) 转换&#xff08;transform&#xff09; 是CSS3中具有颠覆性的特征之一&#xff0c;可以实现元素的位移、旋转、缩放等效果 移动&#xff1a;translate 旋转&#xff1a;rotate 缩放&#xff1a;scale 下图为2D转换的坐标系 回忆…

【笔记】在 MSYS2(MINGW64)中安装 python-maturin 的记录

#工作记录 &#x1f4cc; 安装背景 操作系统&#xff1a;MSYS2 MINGW64当前时间&#xff1a;2025年6月1日Python 版本&#xff1a;3.12&#xff08;通过 pacman 安装&#xff09;目标工具&#xff1a;maturin —— 用于构建和发布 Rust 编写的 Python 包 &#x1f6e0;️ 安装…

基于微信小程序的垃圾分类系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

工作日记之权限校验-token的实战案例

背景说明 我们组负责维护的一个系统&#xff0c;前端界面挂载在其他两个系统上&#xff0c;因为历史遗留原因&#xff0c;同时也挂在公网上&#xff0c;没有登陆功能和用户体系&#xff0c;只要输入网址就能访问&#xff0c;虽然这个系统是给公司内部人员使用&#xff0c;但是…

mysql双主模式下基于keepalived的虚拟ip实现高可用模式搭建

数据库安装和升级和双主配置的操作可以参考我的另一篇文章&#xff1a; 数据库安装和升级和双主配置 1、在两台服务器都下载和安装keepalived 下载&#xff1a; yumdownloader --resolve keepalived 下载后得到&#xff1a; [rootlocalhost keepalivedRpm]# ll 总用量 1896 …

展会聚焦丨漫途科技亮相2025西北水务博览会!

2025第三届西北水务数字化发展论坛暨供排水节水灌溉新技术设备博览会在兰州甘肃国际会展中心圆满落幕。本届展会以“科技赋能水资源&#xff0c;数智引领新动能”为主题&#xff0c;活动汇集水务集团、科研院所、技术供应商等全产业链参与者&#xff0c;旨在通过前沿技术展示与…

单调栈(打卡)

本篇基于b站灵茶山艾府。 下面是灵神上课讲解的题目与课后作业&#xff0c;课后作业还有三道实在写不下去了&#xff0c;下次再写。 739. 每日温度 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是…

【机器学习基础】机器学习入门核心算法:层次聚类算法(AGNES算法和 DIANA算法)

机器学习入门核心算法&#xff1a;层次聚类算法&#xff08;AGNES算法和 DIANA算法&#xff09; 一、算法逻辑二、算法原理与数学推导1. 距离度量2. 簇间距离计算&#xff08;连接标准&#xff09;3. 算法伪代码&#xff08;凝聚式&#xff09; 三、模型评估1. 内部评估指标2. …

已有的前端项目打包到tauri运行(windows)

1.打包前端项目产生静态html、css、js 我们接下来用vue3 vite编写一个番茄钟案例来演示。 我们执行npm run build 命令产生的dist目录下的静态文件。 2.创建tarui项目 npm create tauri-applatest一路回车&#xff0c;直到出现。 3.启动运行 我们将打包产生的dist目录下的…

Unity3D仿星露谷物语开发55之保存地面属性到文件

1、目标 将游戏保存到文件&#xff0c;并从文件中加载游戏。 Player在游戏中种植的Crop&#xff0c;我们希望保存到文件中&#xff0c;当游戏重新加载时Crop的GridProperty数据仍然存在。这次主要实现保存地面属性&#xff08;GridProperties&#xff09;信息。 我们要做的是…

Java面试:企业协同SaaS中的技术挑战与解决方案

Java面试&#xff1a;企业协同SaaS中的技术挑战与解决方案 面试场景 在一家知名互联网大厂&#xff0c;面试官老王正在对一位应聘企业协同SaaS开发职位的程序员谢飞机进行技术面试。 第一轮提问&#xff1a;基础技术 老王&#xff1a;谢飞机&#xff0c;你好。首先&#xf…

SQL注入速查表(含不同数据库攻击方式与差异对比)

1. 字符串连接 字符串连接是SQL注入中常用的操作&#xff0c;用于将多个字符串拼接为一个&#xff0c;以构造复杂的注入语句。不同数据库的字符串连接语法存在显著差异&#xff0c;了解这些差异有助于精准构造payload。 Oracle&#xff1a;使用||操作符进行字符串连接&#xf…

uni-data-picker级联选择器、fastadmin后端api

记录一个部门及部门人员选择的功能&#xff0c;效果如下&#xff1a; 组件用到了uni-ui的级联选择uni-data-picker 开发文档&#xff1a;uni-app官网 组件要求的数据格式如下&#xff1a; 后端使用的是fastadmin&#xff0c;需要用到fastadmin自带的tree类生成部门树 &#x…

Mac电脑上本地安装 redis并配置开启自启完整流程

文章目录 一、安装 Redis方法 1&#xff1a;通过源码编译安装&#xff08;推荐&#xff09;方法 2&#xff1a;通过 Homebrew 安装&#xff08;可选&#xff09; 二、配置 Redis1. 创建配置文件和数据目录2. 修改配置文件 三、配置开机自启1、通过 launchd 系统服务&#xff08…

wsl安装linux

安装wsl 启用适用于 Linux 的 Windows 子系统 以管理员身份打开 PowerShell &#xff08;> PowerShell > 右键单击 > 以管理员身份运行&#xff09; 并输入以下命令&#xff0c;然后重启 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsyste…