自定义命令行解释器shell

目录

一、模块框架图

二、实现目标

三、实现原理

四、全局变量 

五、环境变量函数 

六、初始化环境变量表函数

七、输出命令行提示符模块

八、提取命令输入模块

九、填充命令行参数表模块

十、检测并处理内建命令模块

十一、执行命令模块

十二、源码


一、模块框架图

二、实现目标

  • 要能处理普通命令
  • 要能处理内建命令
  • 要能帮助我们理解内建命令/本地变量/环境变量这些概念
  • 要能帮助我们理解shell的允许原理

三、实现原理

# 考虑下面这个与shell典型的互动:

[root@localhost epoll]# lsclient.cpp  readme.md  server.cpp  utility.h[root@localhost epoll]# psPID TTY          
TIME CMD3451 pts/0    
3514 pts/0    
00:00:00 bash00:00:00 ps

# ⽤下图的时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为sh的⽅块代表,它随着时间的流逝从左向右移动。shell从⽤⼾读⼊字符串"ls"。shell建⽴⼀个新的进程,然后在那个进程中运⾏ls程序并等待那个进程结束。

# 然后shell读取新的⼀⾏输⼊,建⽴⼀个新的进程,在这个进程中运⾏程序 并等待这个进程结束。

四、全局变量 

  • 我们的shell内部有两张表命令行参数表和环境变量表
  • 同时我们还要定义一张哈希表方便处理别名
  • 定义两个数组用来方便处理记录路径
  • lastcode记录上一次的进程退出码、
  • 宏定义大小方便开辟数组 以及命令行输出格式
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "// 下面是shell定义的全局数据// 1. 命令行参数表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;// 2. 环境变量表
#define MAX_ENVS 100
char* g_env[MAX_ENVS];
int g_envs = 0;// 3. 别名映射表
std::unordered_map<std::string, std::string> alias_list;// for test
char cwd[1024];
char cwdenv[1024];// last exit code
int lastcode = 0;

五、环境变量函数 

  •  环境标量函数直接调用getenv获取在返回即可
  • 注意GetPWD需要ssnprintf格式化写入即可
  • DirName直接从后面查找分割符\ 然后返回之后的字符串即可
const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}const char* GetPwd()
{//const char *pwd = getenv("PWD");const char* pwd = getcwd(cwd, sizeof(cwd));if (pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}
string DirName(const char* pwd)
{
#define SEP "/"string ret = pwd;int index = ret.rfind(SEP);if (ret == SEP){return SEP;}if (index == string::npos){return "BUG?";}string t = ret.substr(index+1);return t;
}

六、初始化环境变量表函数

  • 直接把enriron指向的环境变量表拷贝到我们的环境变量表里面 为了区分我们的shell和系统的我们在末尾添加上haha区分
  • 再让environ指向我们的环境变量表即可。
void InitEnv()
{extern char** environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;for (int i = 0; environ[i]; i++){g_env[i] = (char*)malloc(strlen(environ[i]) + 1);strcpy(g_env[i], environ[i]);g_envs++;}g_env[g_envs++] = "haha";g_env[g_envs] = NULL;for (int i = 0; g_env[i]; i++){putenv(g_env[i]);cout << g_env[i] << endl;}environ = g_env;
}

七、输出命令行提示符模块

  • 先定义一个字符输出存储命令行提示符
  • 然后snprintf格式化写入字符数组中 在输出即可。
void PrintCommandPrompt()
{char cmd_prompt[COMMAND_SIZE];MakeCommandLine(cmd_prompt, sizeof(cmd_prompt));std::cout << cmd_prompt;
}
void MakeCommandLine(char cmd_prompt[], int size)
{snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}
const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}const char* GetPwd()
{//const char *pwd = getenv("PWD");const char* pwd = getcwd(cwd, sizeof(cwd));if (pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}
string DirName(const char* pwd)
{
#define SEP "/"string ret = pwd;int index = ret.rfind(SEP);if (ret == SEP){return SEP;}if (index == string::npos){return "BUG?";}string t = ret.substr(index+1);return t;
}

八、提取命令输入模块

  • 这里先fgets获取标准输入到字符数组中
  • 然后构造字符串删除erase\0 然后去map中判断是否为别名
  • 如果是直接把value的值拷贝到数组中即可 然后return ture退出
  • 不是别名则把用户回车的\n字符消除 同时如果用户只回车此时n==0
  • 不做处理返回false 其他返回true;
bool GetCommandLine(char* commandline, int size)
{const char* ret=fgets(commandline, size, stdin);if (ret == NULL){return false;}std::string a = ret;a.erase(a.size() - 1, 1);if (alias_list.count(a)){strcpy(commandline, alias_list[a].c_str());return true;}int n = strlen(commandline);commandline[n - 1] = 0;if (n == 0){return false;}return true;
}

九、填充命令行参数表模块

  • 先strtok获取指向第一个空格字符串
  • while的那个ret不为空时填充g_argv参数表
  • 继续分割填充,直到找不到空格,说明字符串分割完毕
  • 最后填充在g_argv表最后填充NULL即可
  • 根据g_argv大小判断是否填充成功 成功返回ture 反之返回flase 
bool CommandParse(char* commandline)
{
#define SEP " "g_argc = 0;char* ret = strtok(commandline, SEP);if (ret == NULL){return false;}while (ret){g_argv[g_argc++] = ret;ret = strtok(nullptr, SEP);}g_argv[g_argc] = ret;for (int i = 0; g_argv[i]; i++){cout << g_argv[i] << " ";}cout << endl;return g_argc > 0 ? true : false;
}

十、检测并处理内建命令模块

# 先根据g_argv表的第一个命令 判断分流检测处理

  • cd命令如果只有cd 那就直接获取家目录的环境变量字符串
  • chdir修改当前命令为家目录即可
  • 否则直接chdir修改当前目录的路径为g_argv[1]
  • echo命令判断分流
  • echo $?直接返回lastcode退出码 再设置wield0即可
  • echo $xxx 直接获取xxx的环境变量 再输出即可
  • echo xxx 直接打印xxx字符串g_argv[1]即可
  • export命令先判断是否填写了要导入的环境变量
  • 没有直接返回ture不做处理 否则直接putenv导入g_argv[1]环境变量即可
  • alias命令 这里只处理不带命令选项的替换
  • strtok分割=前后字符串然后 存储到map中即可

bool CheckAndExecBuiltion()
{std::string t = g_argv[0];if (t == "cd"){return Cd();}else if (t == "echo"){return Echo();}else if (t == "export"){return Export();}else if (t == "alias"){cout << "开始替换" << endl;return Alias();}else{return false;}
}
bool Cd()
{std::string t;if (g_argc == 1){t = GetHome();if (t == ""){return true;}chdir(t.c_str());}else{t =g_argv[1];chdir(t.c_str());}return true;
}
bool Echo()
{std::string t = g_argv[1];if (t=="$?"){std::cout << lastcode << std::endl;lastcode = 0;}else if (t[0] == '$'){char* ret = getenv(t.substr(1).c_str());if (ret){cout << ret << endl;}}else{cout << t << endl;}return true;
}
bool Export()
{if (g_argc != 2){return true;}putenv(g_argv[1]);return true;
}
bool Alias()
{
#define SEP "="char* t = g_argv[1];char* ret = strtok(t, SEP);if (ret == NULL){return false;}string a=std::string(ret),b = std::string(strtok(NULL, SEP));alias_list[a] = b;cout << alias_list[a] <<" "<<alias_list.count(a)<<endl;cout << a<< "->" << b << endl;return true;
}

十一、执行命令模块

  • 直接创建子进程,子进程通过execvp程序替换执行命令,执行完后exit退出
  • 然后父进程waitpid等待子进程,同时把lastcode更新即可
int Execute()
{pid_t id = fork();if (id == 0){execvp(g_argv[0], g_argv);exit(1);}int status = 0;pid_t rid=waitpid(id, &status, 0);if (rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}

    十二、源码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/wait.h>
    #include<unordered_map>#define COMMAND_SIZE 1024
    #define FORMAT "[%s@%s:%s]# "// 下面是shell定义的全局数据//1. 命令行参数表
    #define MAXARGC 128
    char *g_argv[MAXARGC]; 
    int g_argc = 0;//2. 环境变量表
    #define MAX_ENVS 100
    char* g_env[MAX_ENVS];
    int g_envs = 0;//3. 别名映射表
    std::unordered_map<std::string, std::string> alias_list;// for test
    char cwd[1024];
    char cwdenv[1024];// last exit code
    int lastcode = 0;//获取用户名
    const char *GetUserName()
    {const char *name = getenv("USER");return name == NULL ? "None" : name;
    }//获取主机名
    const char *GetHostName()
    {const char *hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
    }//获取当前路径
    const char *GetPwd()
    {//const char *pwd = getenv("PWD");const char *pwd = getcwd(cwd, sizeof(cwd));if(pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
    }//获取家目录
    const char *GetHome()
    {const char *home = getenv("HOME");return home == NULL ? "" : home;
    }//初始化环境变量
    void InitEnv()
    {extern char **environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;//本来要从配置文件中来//1. 获取环境变量for (int i = 0; environ[i]; i++){// 1.1 申请空间g_env[i] = (char*)malloc(strlen(environ[i]) + 1);strcpy(g_env[i], environ[i]);g_envs++;}g_env[g_envs++] = (char*)"HAHA=for_test"; //for_testg_env[g_envs] = NULL;//2. 导成环境变量for (int i = 0; g_env[i]; i++){putenv(g_env[i]);std::cout << g_env[i] << std::endl;}environ = g_env;
    }//command
    bool Cd()
    {if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else{std::string where = g_argv[1];// cd - / cd ~if(where == "-"){// Todo}else if(where == "~"){// Todo}else{chdir(where.c_str());}}return true;
    }void Echo()
    {if(g_argc == 2){// echo "hello tata"// echo $?// echo $PATHstd::string opt = g_argv[1];if(opt == "$?"){std::cout << lastcode << std::endl;lastcode = 0;}else if(opt[0] == '$'){std::string env_name = opt.substr(1);const char *env_value = getenv(env_name.c_str());if(env_value)std::cout << env_value << std::endl;			}else{std::cout << opt << std::endl;}}
    }bool Export()
    {if (g_argc != 2){return false;}putenv(g_argv[1]);return true;
    }bool Alias()
    {char* t = g_argv[1];char* ret = strtok(t, "=");if (ret == NULL){return false;}string a=std::string(ret),b = std::string(strtok(NULL, "="));alias_list[a] = b;std::cout << alias_list[a] << " " << alias_list.count(a) << std::endl;std::cout << a << "->" << b << std::endl;return true;
    }//绝对路径改为目录名
    std::string Dirname(const char *pwd)
    {
    #define SLASH "/"std::string dir = pwd;if(dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if(pos == std::string::npos) return "BUG?";return dir.substr(pos+1); // +1是为了去掉目录名前面的/
    }//制作命令行提示符
    void MakeCommandLine(char cmd_prompt[], int size)
    {snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), Dirname(GetPwd()).c_str());
    }//打印命令行提示符
    void PrintCommand()
    {char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
    }//判断输入内容
    bool GetCommandLine(char *out, int size)
    {//scanf()是以空格作为分隔符的,但是我们的命令是一整个字符串//ls -a -l => "ls -a -l\n”字符串char *c = fgets(out, size, stdin);if(c == NULL) return false;out[strlen(out)-1] = 0; // 清理\nif(strlen(out) == 0) return false;return true;
    }//命令行分析 "ls -a -l" => "ls" "-a" "-l"
    bool CommandParse(char *commandline)
    {
    #define SEP " " //分隔符g_argc = 0;g_argv[g_argc++] = strtok(commandline, SEP);while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));g_argc--; // NULL不算,去掉return g_argc > 0 ? true:false;
    }//测试命令行参数分割,打印
    void PrintArgv()
    {for(int i = 0; g_argv[i]; i++){printf("argv[%d]->%s\n", i, g_argv[i]);}printf("g_argc:%d\n", g_argc);
    }//处理并执行内建命令
    bool CheckAndExcuteBuiltn()
    {std::string cmd = g_argv[0];if(cmd == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}else if(cmd == "export"){Export();return true;}else if(cmd == "alias"){std::cout << "开始替换" << std::endl;return Alias();}elsereturn false;
    }//执行命令
    int Execute()
    {pid_t id = fork();if(id == 0){//childexecvp(g_argv[0], g_argv);exit(1);}int status = 0;//fathepid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}return 0;
    }int main()
    {// shell启动的时候需要从系统中获取环境变量// 我们的环境变量信息应该从父shell统一来InitEnv();while(true)//持续运行{// 1. 输出命令行提示符PrintCommand();// 2. 获取用户输入的命令char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline)))continue;//什么都没输入直接回车,继续运行printf("echo %s\n", commandline);// 3. 命令行分析 "ls -a -l" => "ls" "-a" "-l"if(!CommandParse(commandline))continue;//PrintArgv(); //测试// 4. 检测并处理内建命令if(CheckAndExcuteBuiltn())continue;// 5. 执行命令Execute();}// ClearUp();return 0;
    }

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
    如若转载,请注明出处:http://www.pswp.cn/diannao/92144.shtml
    繁体地址,请注明出处:http://hk.pswp.cn/diannao/92144.shtml

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

    相关文章

    uniapp使用uni-ui怎么修改默认的css样式比如多选框及样式覆盖小程序/安卓/ios兼容问题

    修改 uni-ui 多选框 (uni-data-checkbox) 的默认样式 在 uniapp 中使用 uni-ui 的 uni-data-checkbox 组件时&#xff0c;可以通过以下几种方式修改其默认样式&#xff1a; 方法一&#xff1a;使用深度选择器格式一&#xff1a;在页面的 style 部分使用深度选择器 >>>…

    《Linux 环境下 Nginx 多站点综合实践:域名解析、访问控制与 HTTPS 加密部署》​

    综合练习:请给openlab搭建web网站&#xff0c;网站需求&#xff1a; 1.基于域名www.openlab.com可以访问网站内容为 welcome to openlab!!&#xff0c; 2.给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料和缴费网站&#xff0c;基于www.openlab.com/student 网站访…

    网络基础1-11综合实验(eNSP):vlan/DHCP/Web/HTTP/动态PAT/静态NAT

    注&#xff1a;在华为模拟器&#xff08;eNSP&#xff09;上做的实验其中&#xff0c;在内网实验&#xff1a;Vlan/DHCP/VWeb/HTTP&#xff0c;在外网实验&#xff1a;动态PAT/静态NAT一、拓扑结构1. 核心设备与连接设备接口连接对象VLAN/IP角色LSW2/LSW3Ethernet 0/0/1-2PC1/P…

    Mac上安装Claude Code的步骤

    以下是基于现有信息的简明安装指南&#xff0c;适用于macOS系统。请按照以下步骤操作&#xff1a; 前提条件 操作系统&#xff1a;macOS 10.15或更高版本。Node.js和npm&#xff1a;Claude Code基于Node.js&#xff0c;需安装Node.js 18和npm。请检查是否已安装&#xff1a; …

    MybatisPlus-15.扩展功能-逻辑删除

    一.逻辑删除配置逻辑删除的字段时&#xff0c;logic-delete-field字段配置的是逻辑删除的实体字段名。字段类型可以是boolean和integer。在java中默认是boolean类型。逻辑已删除值默认为1&#xff0c;而逻辑未删除值默认为0。当是1时代表已删除(1在数据库表中为true&#xff0c…

    IDEA 同时修改某个区域内所有相同变量名

    在 IntelliJ IDEA 中&#xff0c;同时修改某个区域内所有 相同变量名 的快捷键是&#xff1a; ✅ Shift F6&#xff08;重命名变量&#xff09; 但这个快捷键默认是 全局重命名&#xff0c;如果你想 仅修改某个方法或代码块内的变量名&#xff0c;可以这样做&#xff1a;&…

    Telink BLE 低功耗学习

    低功耗管理&#xff08;Low Power Management&#xff09;也可以称为功耗管理&#xff08;Power Management&#xff09;&#xff0c;本⽂档中会简称为PM。Telink低功耗解惑我查阅多连接SDK开发手册时&#xff0c;低功耗管理章节看了两三遍也没太明白&#xff0c;有以下几个问题…

    设备管理系统(MMS)如何在工厂MOM功能设计和系统落地

    一、核心系统功能模块设备管理系统围绕设备全生命周期管理设计&#xff0c;涵盖基础数据管理、设备运维全流程管控及统计分析功能&#xff0c;具体如下&#xff1a;基础数据管理设备与备件台账&#xff1a;包含设备台账&#xff08;设备编号、识别码、型号、生产日期等&#xf…

    低空经济展 | 牧羽天航空携飞行重卡AT1300亮相2025深圳eVTOL展

    为深入推动低空经济产业高质量发展&#xff0c;构建全球eVTOL&#xff08;电动垂直起降飞行器&#xff09;产业交流合作高端平台&#xff0c;2025深圳eVTOL展定于2025年9月23日至25日在深圳坪山燕子湖国际会展中心隆重举办。本届展会以“低空经济・eVTOL・航空应急救援・商载大…

    CS231n-2017 Lecture4神经网络笔记

    神经网络&#xff1a;我们之前的线性分类器可以接受输入&#xff0c;进而给出评分&#xff0c;这是一种线性变换&#xff0c;再此基础上&#xff0c;我们对这种线性变换结果进行非线性变换&#xff0c;并输入到下一层线性分类器中&#xff0c;这个过程就像是人类大脑神经的运作…

    暑期算法训练.5

    目录 20. 力扣 34.在排序数组中查找元素的第一个位置和最后一个位置 20.1 题目解析&#xff1a; 20.2 算法思路&#xff1a; 20.3 代码演示&#xff1a; ​编辑 20.4 总结反思&#xff1a; 21.力扣 69.x的平方根 21.1 题目解析&#xff1a; 21.2 算法思路&#xff1a;…

    【HDLBits习题详解 2】Circuit - Sequential Logic(5)Finite State Machines 更新中...

    1. Fsm1&#xff08;Simple FSM 1 - asynchronous reset&#xff09;状态机可分为两类&#xff1a;&#xff08;1&#xff09;Mealy状态机&#xff1a;输出由当前状态和输入共同决定。输入变化可能立即改变输出。&#xff08;2&#xff09;Moore状态机&#xff1a;输出仅由当前…

    多级缓存(亿级流量缓存)

    传统缓存方案问题 多级缓存方案 流程 1.客户端浏览器缓存页面静态资源; 2. 客户端请求到Nginx反向代理;[一级缓存_浏览器缓存] 3.Nginx反向代理将请求分发到Nginx集群(OpenResty); 4.先重Nginx集群OpenResty中获取Nginx本地缓存数据;[二级缓存_Nginx本地缓存] 5.若Nginx本地缓存…

    浅谈Rust语言特性

    如大家所了解的&#xff0c;Rust是一种由Mozilla开发的系统编程语言&#xff0c;专注于内存安全、并发性和高性能&#xff0c;旨在替代C/C等传统系统编程语言。Rust 有着非常优秀的特性&#xff0c;例如&#xff1a;可重用模块 内存安全和保证&#xff08;安全的操作与不安全的…

    React探索高性能Tree树组件实现——react-window、react-vtree

    &#x1f680; 简介 在现代 Web 应用中&#xff0c;处理大量层级数据的树形结构是一个常见挑战。传统的树组件在面对成千上万个节点时往往会出现性能瓶颈&#xff0c;导致页面卡顿、内存占用过高等问题。本文将深入探讨如何使用 react-window 和 react-vtree 构建高性能的虚拟…

    C++ 中的默认构造函数:非必要,不提供

    《More Effective C&#xff1a;35个改善编程与设计的有效方法》 读书笔记&#xff1a;非必要不提供default constructor在 C 中&#xff0c;默认构造函数&#xff08;即无需任何参数即可调用的构造函数&#xff09;是对象“无中生有”的一种方式。它的核心作用是在没有外部信息…

    如何选择低代码开发平台

    选择低代码开发平台需要考虑平台的开发效率、灵活性和扩展能力、安全性和合规性、成本效益等关键因素。 具体来说&#xff0c;平台的灵活性和扩展能力尤为重要&#xff0c;这决定了平台是否能长期满足企业日益增长的复杂需求。例如&#xff0c;企业在评估平台时&#xff0c;应关…

    电子数据取证领域的双轮驱动——手工分析 vs 自动化分析

    在你刚步入电子数据取证领域时&#xff0c;可能很快就注意到一个普遍现象&#xff1a;大多数取证分析师前期都花费大量时间在网上查阅博客、PDF、推文等信息&#xff0c;寻找证据线索的“藏身之处”——例如注册表项、日志文件路径、可疑文件命名模式或远程登录痕迹等。这种信息…

    《Python 实时通信全解:掌握 WebSocket 技术与 HTTP 的本质区别》

    🚀《Python 实时通信全解:掌握 WebSocket 技术与 HTTP 的本质区别》 引言:通信方式的演进与 Python 的角色 在数字化世界里,**“实时性”**已经成为构建高质量应用的核心诉求。从聊天工具到股票交易系统,再到物联网设备管理——通信的即时响应能力直接决定用户体验。而…

    GeoTools 自定义坐标系

    前言在GIS开发中&#xff0c;坐标系统是重中之重&#xff0c;在接到任务时首先要确定的就是坐标系。大多数地图库或者互联网地图默认支持WGS84地理坐标系和Web墨卡托投影坐标系。而在我国要求使用自然资源数据使用2000国家大地坐标&#xff08;CGCS2000&#xff09;。1. 背景 经…