【Linux】进程控制(四)—— 手搓自主shell
学习Linux到目前为止我们都知道命令是由shell执行的但是具体如何执行的我们看不到因此我们今天来自己写一个shell来执行我们的指令让大家对shell的底层有一个进阶的理解文章的最后会给出完整代码喔~目录一、打印命令行提示符二、获取键盘输入三、解析字符串四、执行指令五、增加内建命令1.cd 路径的改变2.echo 退出码六、总结与源码一、打印命令行提示符知识前置shell本质上是一个死循环因为要不断地处理一条又一条指令因此编写的shell功能全部都要放进一个死循环中直到我们主动退出才结束命令行提示符的格式为 [用户名主机名 路径]因此要打印出来就必须获取这三个数据很显然它们都属于环境变量分别对应USER、HOSTNAME和PWD要获取环境变量用getenv函数即可为了方便管理我们将打印命令行提示符封装成一个函数//1.打印命令行提示符 PrintCommandLine();void PrintCommandLine() { printf([%s%s %s]# , GetUserName(), GetHostName(), GetPwd()); //用户名主机名 当前路径 }const char *GetUserName() { char *name getenv(USER); if(name NULL) { return None; } return name; } const char *GetHostName() { char *hostname getenv(HOSTNAME); if(hostname NULL) { return None; } return hostname; } const char *GetPwd() { char *pwd getenv(PWD); if(pwd NULL) { return None; } return pwd; }至此命令行提示符的打印就完成了但是shell本质是一个死循环它还得需要一个等待我们输入指令的功能否则就会一直打印导致满屏的命令行提示符二、获取键盘输入键盘输入本质就是输入一个字符串需要用一个字符数组来接收#define MAXSIZE 128 //... char command_line[MAXSIZE] {0};对于指令的输入我们通常存在两种情况一种是输入指令后回车一种是啥也不输入直接回车因此要获取键盘输入我们需要用到系统调用 fgets 啥也不输入的时候返回NULL 我们依旧对获取键盘输入的功能封装一个函数且函数的返回值为输入的指令字符串长度当返回0的时候命中我们刚刚说的第二种情况直接continue即可否则我们尝试打印出刚刚输入的指令做一个验证测试看看是否输出的与我们输入的一致但同时要注意的是无论哪一种情况我们都要至少输入一次回车键回车键相当于换行符\n因此为了保证字符串和输出的正确性我们需要将最后的换行符改成\0//2.获取键盘输入 if(GetCommand(command_line, sizeof(command_line)) 0) continue; printf(%s\n, command_line);int GetCommand(char commandline[], int size) { if(fgets(commandline, size, stdin) NULL) return 0; //用户输入的时候至少会按一次回车\n改\0 commandline[strlen(commandline)-1] \0; return strlen(commandline); }运行结果如下三、解析字符串前面我们输入进去的指令是一整个字符串我们要把它们拆分“ls -a -l” - “ls” “-a” -l 并放入命令行参数表中对于字符串的拆分C语言中有一个封装好的函数strtok第一个参数为要拆分的字符串第二个参数为拆分符号遇到该符号就进行拆分对于同一个字符串的第二次拆分则将第一个参数设为NULL否则会一直拆分第一个而后面的不拆分具体代码演示如下#includestdio.h #includestring.h int main() { char str[] aaa bbb ccc ddd; const char* sep ; char *p strtok(str, sep); printf(%s\n, p); while(p) { p strtok(NULL, sep); if(p NULL) { break; } printf(%s\n,p); } return 0; }拆分的字符串我们要放到全局的环境变量表中这是shell内部要维护的第一张表同时设置一下切割分隔符#define MAXARGS 32 //shell内部维护的第一张表命令行参数表 char *gargv[MAXARGS]; int gargc 0; const char *sep ;将解析字符串封装函数//3.解析字符串 ParseCommand(command_line);int ParseCommand(char commandline[]) { //输入新的指令要重置命令行参数表 gargc 0; memset(gargv, 0, sizeof(gargv)); //分割字符串 gargv[0] strtok(commandline, sep); while((gargv[gargc] strtok(NULL, sep))); //打印测试 printf(gargc: %d\n, gargc); int i 0; for(; gargv[i]; i) printf(gargv[%d]: %s\n, i, gargv[i]); return 0; }我们打印出分割结果测试一下效果四、执行指令前面我们完成了指令的输入和解析还差一个执行要执行指令就需要fork子进程来进行程序替换//4.执行指令 ExcuteCommand();int ExcuteCommand() { pid_t id fork(); if(id 0) return -1; else if(id 0) { //子进程 程序替换 execvp(gargv[0], gargv); exit(1); } else{ //父进程 int status 0; pid_t rid waitpid(id, status, 0); if(rid 0) { printf(wait child process success!\n); } } }有了命令行参数表gargv我们就可以用程序替换函数execvp了对于程序替换有问题的可以回看博主的文章《进程程序替换》【Linux】进程控制三——进程程序替换-CSDN博客我们来看看运行结果结果如我们所料我们把所有的测试打印全部注释再试试看到这里为止shell的基本框架搭成了五、增加内建命令刚刚我们输入的指令都如期由子进程替换执行成功了但是我们来看下面这种情况这里演示了两种无法成功执行的情况echo $?是输出上一个程序执行结束的退出码这里没有打印出来cd ..是返回上一个目录但是我们用pwd查看却发现路径没有变化。原因就是它们都属于内建命令内建命令的特点是由Shell自身解析执行不需要创建新进程再替换接下来一一解决这两个问题执行命令前先写一个函数判断是否是内建命令如果是则直接执行并返回1如果不是则返回0并创建子进程替换执行命令1.cd 路径的改变要改变路径可以使用chdir函数直接将gargv[1]放入参数即可因为gargv[0]是cd后面一个必跟路径//4.执行指令 if(CheckBuiltinExcute() 0) continue; ExcuteCommand();int CheckBuiltinExcute() { if(strcmp(gargv[0], cd) 0) { //内建命令 if(gargc 2) { //新的目标路径 chdir(gargv[1]); } return 1; } return 0; }试一下执行结果可以发现路径改变了但是为什么命令行提示符的路径却没有改变呢回看我们之前获取路径的函数const char *GetPwd() { char *pwd getenv(PWD); if(pwd NULL) { return None; } return pwd; }会发现我们获取的是环境变量的PWD记录但是环境变量的这个值是静态的即使我们用chdir切换了目录但是没更新PWD环境变量它依旧会返回旧路径不准要实时改变这个路径就不能依赖环境变量而需要一个能直接与内核交互的系统调用 getcwd这个系统调用的第一个参数属于典型的输出型参数我们提供数组参数它会将实时路径给我传递到数组中那我们就创建一个接收的数组//我们shell所处的工作路径 char cwd[MAXSIZE];利用getcwd系统调用优化Getpwd函数const char *GetPwd() { //char *pwd getenv(PWD); char *pwd getcwd(cwd, sizeof(cwd)); if(pwd NULL) { return None; } return cwd; }试试优化后的效果达到了我们的预期但到这里还剩下最后一个问题Linux命令行提示符中的路径只保留了最后一个而我们是直接显示出了一长串的绝对路径这是过于冗余的我们接下来就是要解决这个问题只取它的最后一个“/”后的路径这里我用c来实现static std::string rfindDir(const std::string p) { if(p /) return p; const std::string psep /; auto pos p.rfind(psep); if(pos std::string::npos) { return std::string(); } return p.substr(pos1); }在打印的位置将本来要传的绝对路径先传入这个函数当中最后截取结束后再打印出来void PrintCommandLine() { printf([%s%s %s]# , GetUserName(), GetHostName(), rfindDir(GetPwd()).c_str()); //用户名主机名 当前路径 }记得对rfindDir的返回值还要加一个c_str()这是C为了兼容C语言的打印而设计的接口来看看成果为了区分原shell和我们自己写的shell我们的分隔符是不一样的前者是$我的是#到这里就解决了cd路径的改变问题所以其实真正的shell还是非常复杂的我这仅仅是极简版本还有很多内建命令和其他各种快捷键功能没实现我们接下来再解决一下echo问题2.echo 退出码echo依旧是内建命令承接实现“cd”的代码继续扩充。首先先解决获取退出码的问题先定一个全局变量Last_Exitcode//上一个进程结束的退出码 int Last_ExitCode 0;编写输入指令为echo的情况并在每个指令执行结束的地方重置退出码int CheckBuiltinExcute() { if(strcmp(gargv[0], cd) 0) { //内建命令 if(gargc 2) { //新的目标路径 chdir(gargv[1]); Last_ExitCode 0; } return 1; } else if(strcmp(gargv[0], echo) 0) { if(gargc 2) { if(gargv[1][0] $) { if(strcmp(gargv[1]1, ?) 0) { printf(lastcode:%d\n, Last_ExitCode); } Last_ExitCode 0; } return 1; } } return 0; }//父进程 int status 0; pid_t rid waitpid(id, status, 0); if(rid 0) { //获取退出码 Last_ExitCode WEXITSTATUS(status); //printf(wait child process success!\n); }来看看实现成果六、总结与源码至此一个简易的shell被我们手搓出来了独立完成其实非常考验知识储备和代码能力可作为一个教学意义极高的训练典例其实除了命令行参数表以外shell内部还管理了另一张表就是环境变量表它也承担着非常重要的角色我将手搓的源码放在下面供大家自行在此基础拓展#includestdio.h #includestdlib.h #includestring.h #includeunistd.h #includesys/types.h #includesys/wait.h #includeiostream #includestring #define MAXSIZE 128 #define MAXARGS 32 //shell内部维护的第一张表命令行参数表 char *gargv[MAXARGS]; int gargc 0; const char *sep ; //我们shell所处的工作路径 char cwd[MAXSIZE]; //上一个进程结束的退出码 int Last_ExitCode 0; static std::string rfindDir(const std::string p) { if(p /) return p; const std::string psep /; auto pos p.rfind(psep); if(pos std::string::npos) { return std::string(); } return p.substr(pos1); } const char *GetUserName() { char *name getenv(USER); if(name NULL) { return None; } return name; } const char *GetHostName() { char *hostname getenv(HOSTNAME); if(hostname NULL) { return None; } return hostname; } const char *GetPwd() { //char *pwd getenv(PWD); char *pwd getcwd(cwd, sizeof(cwd)); if(pwd NULL) { return None; } return cwd; } void PrintCommandLine() { printf([%s%s %s]# , GetUserName(), GetHostName(), rfindDir(GetPwd()).c_str()); //用户名主机名 当前路径 } int GetCommand(char commandline[], int size) { if(fgets(commandline, size, stdin) NULL) return 0; //用户输入的时候至少会按一次回车\n改\0 commandline[strlen(commandline)-1] \0; return strlen(commandline); } int ParseCommand(char commandline[]) { //输入新的指令要重置命令行参数表 gargc 0; memset(gargv, 0, sizeof(gargv)); //分割字符串 gargv[0] strtok(commandline, sep); while((gargv[gargc] strtok(NULL, sep))); // printf(gargc: %d\n, gargc); // int i 0; // for(; gargv[i]; i) // printf(gargv[%d]: %s\n, i, gargv[i]); return 0; } int CheckBuiltinExcute() { if(strcmp(gargv[0], cd) 0) { //内建命令 if(gargc 2) { //新的目标路径 chdir(gargv[1]); Last_ExitCode 0; } return 1; } else if(strcmp(gargv[0], echo) 0) { if(gargc 2) { if(gargv[1][0] $) { if(strcmp(gargv[1]1, ?) 0) { printf(lastcode:%d\n, Last_ExitCode); } Last_ExitCode 0; } return 1; } } return 0; } int ExcuteCommand() { pid_t id fork(); if(id 0) return -1; else if(id 0) { //子进程 程序替换 execvp(gargv[0], gargv); exit(1); } else{ //父进程 int status 0; pid_t rid waitpid(id, status, 0); if(rid 0) { //获取退出码 Last_ExitCode WEXITSTATUS(status); //printf(wait child process success!\n); } } } int main() { char command_line[MAXSIZE] {0}; while(1) { //1.打印命令行提示符 PrintCommandLine(); //2.获取键盘输入 if(GetCommand(command_line, sizeof(command_line)) 0) continue; // printf(%s\n, command_line); //3.解析字符串 ParseCommand(command_line); //4.执行指令 if(CheckBuiltinExcute() 0) continue; ExcuteCommand(); } return 0; }

相关新闻

第六章—18—数据容器的通用操作

第六章—18—数据容器的通用操作

第六章—18—数据容器的通用操作 1.遍历 五类数据容器都支持for循环遍历 列表 元组 字符串支持while循环 (序列类型) 字典 集合不支持(非序列类型) 2不能转为字典(diet),字典要求的是键值对 3从小到大&…

2026/6/24 12:40:24阅读更多 →
怎么跟 AI 说话才能让它写出你要的代码——我和 Claude Code 的配合心法

怎么跟 AI 说话才能让它写出你要的代码——我和 Claude Code 的配合心法

如果你以为 AI 编程就是"你描述需求,AI 啪一下给你完美代码"——我得先泼一盆冷水。 不是这样的。 AI 更像是——一个不会累、反应极快、但时不时会理解歪你意思的实习生。它能力很强,但你需要学会怎么跟它沟通。我把这几个月摸索出来的经验…

2026/6/24 12:40:24阅读更多 →
最便宜稳定 GPT5.5 大模型中转平台

最便宜稳定 GPT5.5 大模型中转平台

最便宜稳定 GPT5.5 大模型中转平台:别只盯着单价,先把账算清楚个人项目或者小团队接 GPT5.5 API 时,最容易踩的坑不是“哪家标价最低”,而是上线两天后发现账单比预期高、请求偶发失败、余额扣费看不懂。选中转平台建议先查三件事…

2026/6/24 12:40:24阅读更多 →
扩散模型在阿尔茨海默病影像生成中的应用与优化

扩散模型在阿尔茨海默病影像生成中的应用与优化

1. 项目概述:当扩散模型遇见阿尔茨海默病影像生成 在神经退行性疾病研究中,阿尔茨海默病(AD)的进展呈现高度个体化特征。传统影像分析方法依赖有限的纵向随访数据,难以全面捕捉疾病发展的动态过程。ADP-DiT的创新之处在…

2026/6/24 16:46:56阅读更多 →
AI服务链路优化:解析OpenAI API网关的Instant工程实践

AI服务链路优化:解析OpenAI API网关的Instant工程实践

1. 项目概述:这不是GPT-5.3,而是对当前AI服务生态的一次清醒认知 “GPT-5.3 Instant 核心亮点,一图看懂!”——看到这个标题,我第一反应是点开前先倒杯水,因为过去三年里,我亲手拆解过27个标榜“…

2026/6/24 16:46:56阅读更多 →
STM32F103硬件输入捕获精准读取DHT11单总线信号

STM32F103硬件输入捕获精准读取DHT11单总线信号

1. 为什么DHT11在STM32F103ZET6上“一上电就报错”?先破除三个常见幻觉你手头刚焊好一块STM32F103ZET6最小系统板,DHT11传感器也接好了——VCC接3.3V、GND接地、DATA接PA0,还加了4.7kΩ上拉电阻。烧录完程序,串口助手上却只刷出一…

2026/6/24 16:46:56阅读更多 →
SOLO:内容意图驱动的AI PPT生产力重构

SOLO:内容意图驱动的AI PPT生产力重构

1. 这不是PPT工具,是“内容-视觉”双线并行的生产力重构 “全新 SOLO 帮我做 PPT,半小时出稿,效果直接惊艳”——这句话在2024年Q2的办公效率类社群里刷屏了三次。第一次是某互联网公司市场部同事发在内部群里的截图:一份含12页数…

2026/6/24 16:46:56阅读更多 →
安卓RAT逆向实战:从环境搭建到动态分析深度拆解AhMyth

安卓RAT逆向实战:从环境搭建到动态分析深度拆解AhMyth

1. 项目概述:为什么我们需要深入剖析AhMyth? 如果你在移动安全领域待过一段时间,或者处理过一些棘手的安卓恶意软件样本,那么“AhMyth”这个名字大概率不会陌生。它不是一个单一的病毒,而是一个臭名昭著的远程访问木马…

2026/6/24 16:46:56阅读更多 →
OpenVAS与Sn1per自动化集成:构建企业级漏洞扫描平台

OpenVAS与Sn1per自动化集成:构建企业级漏洞扫描平台

1. 项目概述:为什么需要整合OpenVAS与Sn1per?在安全运营的日常里,漏洞扫描是基础中的基础,但也是最容易让人头疼的环节之一。很多团队可能都经历过这样的场景:安全工程师用OpenVAS跑完一轮全量扫描,导出一份…

2026/6/24 16:41:53阅读更多 →
【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体 一文搞定到底什么是智能体【人工智能】一文搞定到底什么是智能体一. LM,WorkFlow,Agent分别有什么么不同二. Agent的思考过程是怎样的三. Agent的五个核心部分1)LLM2)Prompt3)Me…

2026/6/24 7:33:03阅读更多 →
嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

1. 嵌入式GUI控件:从原理到实战的深度解析在嵌入式系统开发中,图形用户界面(GUI)的设计与实现往往是项目从“能用”到“好用”的关键一跃。不同于资源充沛的PC或移动平台,嵌入式设备的GUI需要在有限的CPU性能、内存空间…

2026/6/24 2:12:09阅读更多 →
Google AI Studio 300美元额度的真相与实战指南

Google AI Studio 300美元额度的真相与实战指南

1. 这300美金不是“送钱”,而是Google埋下的第一道技术门槛 你看到标题里那个醒目的“$300美金”时,第一反应可能是:又一个免费额度?领完就完事?我亲手试过——这300美金根本不是红包,而是一张入场券&…

2026/6/24 7:37:00阅读更多 →
TaskJuggler脚本编程入门:用代码实现自动化项目管理

TaskJuggler脚本编程入门:用代码实现自动化项目管理

TaskJuggler脚本编程入门:用代码实现自动化项目管理 【免费下载链接】TaskJuggler TaskJuggler - Project Management beyond Gantt chart drawing 项目地址: https://gitcode.com/gh_mirrors/ta/TaskJuggler TaskJuggler是一款强大的开源项目管理工具&#…

2026/6/24 0:02:41阅读更多 →
终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果

终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果

终极教程:使用angular-mobile-nav实现流畅的移动页面过渡效果 【免费下载链接】angular-mobile-nav An angular navigation service for mobile applications 项目地址: https://gitcode.com/gh_mirrors/an/angular-mobile-nav angular-mobile-nav是一款专为…

2026/6/24 0:02:41阅读更多 →
Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作

Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作

Wan2.1-Fun-V1.1-1.3B-InP Web UI使用教程:无需代码的AI视频创作 【免费下载链接】Wan2.1-Fun-V1.1-1.3B-InP 项目地址: https://ai.gitcode.com/hf_mirrors/PAI/Wan2.1-Fun-V1.1-1.3B-InP Wan2.1-Fun-V1.1-1.3B-InP是一款强大的AI视频创作工具,…

2026/6/24 0:02:41阅读更多 →