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

自定义命令行解释器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.dtcms.com/a/291521.html

    相关文章:

  • Android开发中Crash治理方案
  • C++中的detach
  • Python打卡Day20 常见的特征筛选算法
  • C 语言的指针复习笔记
  • 圆柱电池自动分选机:全流程自动化检测的革新之路
  • 大模型中的Actor-Critic机制
  • 嵌入式学习笔记--MCU阶段--DAY08总结
  • 【Java基础03】Java变量2
  • seata at使用
  • 自然语言推理技术全景图:从基准数据集到BERT革命
  • 设备虚拟化技术-IRF
  • 利用DeepSeek编写批量输出多个主机的磁盘状况的脚本
  • 携“养鲜”魔法赴中卫,容声冰箱让石头缝里的鲜甜走得更远
  • 前端之学习后端java小白(一)之SDKMAN及helloword
  • EcoVadis评估:为企业带来的多重价值与竞争优势
  • QT跨平台应用程序开发框架(11)—— Qt系统相关
  • STM32F1使用volatile关键字避免内存优化
  • 基于springboot+vue开发的图书馆座位预约系统【源码+sql+可运行】【50721
  • 在安卓开发中,多次点击启动 Service 会有什么问题?
  • 关键成功因素法(CSF)深度解析:从战略目标到数据字典
  • 后训练(Post-training)语言模型
  • NuGet02-包制作及管理
  • 本地部署Nacos开源服务平台,并简单操作实现外部访问,Windows 版本
  • Oracle数据库索引性能机制深度解析:从数据结构到企业实践的系统性知识体系
  • 【python数据结构算法篇】python数据结构
  • 数据库的介绍和安装
  • Qualcomm Linux 蓝牙指南学习--验证 Fluoride 协议栈的功能(2)
  • day59-可观测性建设-zabbix自定义监控项
  • Shell 脚本编程全面学习指南
  • AK视频下载工具:免费高效,多平台支持