【Linux 学习计划】-- 简易版shell编写

目录

思路

创建自己的命令行

获取用户命令

分割命令

检查是否是内建命令

cd命令实现

进程程序替换执行程序

总代码

结语


思路

int main()
{while (1){// 1. 自己的命令行PrintCommandLine();// 2. 获取用户命令char command[SIZE];int n = GetUserCommand(command, sizeof(command));if (n <= 0)return 1;// 3. 分割指令SplitCommand(command);// for(int i = 0; gArgv[i]; i++)//     printf("[%d]: %s\n", i, gArgv[i]);// 4. 检查是否是内建命令n = CheckBuildin();if(n) continue;// 5. 进程程序替换执行命令ExecuteCommand();}return 0;
}

上图中的五个函数,就是我们的思路

首先就是先写命令行:

就是这个,这个获取环境变量 + 指针操作 + 打印即可,较为简单

接着就是需要获取用户输入的指令,并且将其分割,放进数组里面,这里会涉及到strtok函数的使用

最后将我们的函数分割完之后,就是判断是否是内建命令了

如果是内建命令的话,这里就只实现cd和echo $?,我们就单独函数实现

如果不是内建命令的话,我们就正常进行程序替换即可

这里最关键的点就是程序替换了,因为我们要使用的所有的命令都在磁盘上保存着,这时我们fork创建子进程,并用程序替换将磁盘上待替换的程序加载到内存中并将这个子进程给覆盖,最终跑起来

创建自己的命令行

#define SkipPath(p)         \do                      \{                       \p += strlen(p) - 1; \while (*p != '/')   \p--;            \} while (0)const char *GetHome()
{const char *home = getenv("HOME");return home;
}const char *GetHostName()
{const char *hostname = getenv("HOSTNAME");return hostname;
}const char *GetCwd()
{const char *cwd = getenv("PWD");return cwd;
}const char *GetUserName()
{const char *name = getenv("USER");return name;
}void PrintCommandLine()
{char line[SIZE];const char *UserName = GetUserName();const char *Cwd = GetCwd();const char *HostName = GetHostName();SkipPath(Cwd);snprintf(line, sizeof line, "[%s@%s %s]:>", UserName, HostName, strlen(Cwd) == 1 ? "/" : Cwd + 1);printf("%s", line);fflush(stdout);
}

这里没什么好说的,主要就是getenv获取环境变量,我们的参数就是从环境变量中来

需要讲的一点就是do...while(0)那个宏,这里我们如果直接获取PWD的话,就是所有都打出来:

这个是效果

但是整体是这样的

所以我们需要将指针移动到最后面那里,然后只显示最后一段

但是这里为什么用的是宏呢?因为这里是用 c 语言实现的,写一个函数的话还要二级指针,太麻烦了,宏方便点

而使用do...while(0)是为了形成一个代码块,我们可以整体使用,并且在下面调用的时候可以随便加 “ ; ”,这样调用起来就和函数一样了

最终效果如下:

获取用户命令

int GetUserCommand(char command[], size_t n)
{char *s = fgets(command, n, stdin);if (s == NULL)return 2;// 处理 \ncommand[strlen(command) - 1] = ZERO;// printf("命令: %s", command);return strlen(command);
}

其实如果是cpp的话,就直接getline了,但是c语言实现的话我们就用 fgets 获取一行

直接获取即可,完了之后放进提前开辟并传进函数里面的数组中

需要说的一点就是,我们用户输入的指令看起来是这样的:

ls -a -l

但其实最后还会加一个回车表示确定,所以实际上就是这样子的:

ls -a -l\n

综上,我们就需要将最后一个位置设置为\0,上面的ZERO其实就是\0,只不过设置成了宏而已

分割命令

void SplitCommand(char *command)
{gArgv[0] = strtok(command, SEP);int index = 1;while (gArgv[index++] = strtok(NULL, SEP));// 在最后一次判断的时候,发现没有字符串了,strtok 就会返回NULL// 正好让 gArgv 的最后一个位置变为 NULL,这样在后面进程程序替换的时候就可以直接用
}

这里其实就是strtok函数的使用技巧

先说说为什么要分割:

我们获取了程序之后,不是说所有的功能都是我们自己写,像ls、cd、mkdir等等,这些指令在我们电脑中的磁盘上人家都帮我们写好了,shell也是调用的这些文件

所以我们要做的,就是在用户输入指令的时候,我们能将这些程序加载进来

这里就需要用到进程程序替换了,并且我们现在只有用户输入的数据,我们到后面肯定是execvp用起来最好,所以分割完直接放函数里面当参数即可,就实现这个功能了

再来说说 strtok

当你第一次使用strtok的时候,传入一个数组当参数(第一个参数位置,第二个是分隔符)

当你从第二次开始,第一个参数直接传NULL,就代表使用你一刚开始用个的那个数组

这也就是为什么一开始传command数组,后面传NULL的原因

接着最妙的就是,当strtok检测到没有可以分割的了,就会返回NULL,而我们的数组(execvp函数要求)最后一个位置必须以NULL结尾

检查是否是内建命令

int CheckBuildin()
{int yes = 0;const char *enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0){yes = 1;Cd();}else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){yes = 1;printf("%d\n", lastcode);lastcode = 0;}return yes;
}

直接就是if、else检查判断就行,有多少个内建命令就有多少个if、else

cd命令实现

void Cd()
{const char* path = gArgv[1];if(path == NULL) path = GetHome();chdir(path);// 更新环境变量char cwd[SIZE];char temp[SIZE];getcwd(temp, sizeof temp);snprintf(cwd, sizeof(cwd), "PWD=%s", temp);setenv("PWD", cwd, 1);
}

这里主要用到的是chdir函数,ps:chdir支持直接使用 . 或者 ..

先说获取path,程序走到这里,那么用户输入的指令一定是cd,那么就会有像cd ..这样的指令

那么我们获取完之后,分割的指令中,第二个就一定是path,直接获取就好(下标是1)

当然也有可能用户直接输入一个cd,后面啥也不跟,我们随便处理一下,给他返回到家目录即可

最后我们在chdir之后,还需要更新一下路径

这里我们先获取环境变量中的PWD,然后使用snprintf写入数组中,以PWD:%s的格式

最后直接用setenv函数修改(或者说更新)环境变量即可

当然你也可以在最后使用putenv,这样的话,我们的cwd数组就需要开辟在全局,不然函数运行结束之后,cwd数组也就没了,这样putenv找不到他,就会直接异常终止我们的程序,显示段错误(Segmentation fault)

setenv就不用,这里推荐这个,因为他更现代、好用

进程程序替换执行程序

void ExecuteCommand()
{pid_t id = fork();if (id == 0){// 子进程execvp(gArgv[0], gArgv);exit(errno);}else{// 父进程int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){if(WIFEXITED(status) == 0){lastcode = WEXITSTATUS(status);printf("exit code: %d, exit signal:%d\n", WEXITSTATUS(status), WTERMSIG(status));}}}
}

就是execvp函数的使用,创建子进程直接替换,然后父进程在外面waitpid阻塞等待即可,也不用父进程干什么事情

总代码

MyShell.c

点开这个链接,里面就是代码,放在gitee里面了

结语

这篇文章到这里就结束啦!!~( ̄▽ ̄)~*

如果觉得对你有帮助的,可以多多关注一下喔

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

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

相关文章

一个完整的日志收集方案:Elasticsearch + Logstash + Kibana+Filebeat (二)

&#x1f4c4; 本地 Windows 部署 Logstash 连接本地 Elasticsearch 指南 ✅ 目标 在本地 Windows 上安装并运行 Logstash配置 Logstash 将数据发送至本地 Elasticsearch测试数据采集与 ES 存储流程 &#x1f9f0; 前提条件 软件版本要求安装说明Java17Oracle JDK 下载 或 O…

Java使用Selenium反爬虫优化方案

当我们爬取大站的时候&#xff0c;就得需要对抗反爬虫机制的场景&#xff0c;因为项目要求使用Java和Selenium。Selenium通常用于模拟用户操作&#xff0c;但效率较低&#xff0c;所以需要我们结合其他技术来实现高效。 在 Java 中使用 Selenium 进行高效反爬虫对抗时&#xff…

状态管理方案对比与决策

1. 状态管理的基本概念 现代前端应用随着功能复杂度提升&#xff0c;状态管理已成为架构设计的核心挑战。状态管理本质上解决的是数据的存储、变更追踪和响应式更新问题&#xff0c;以确保UI与底层数据保持同步。 核心挑战: 状态共享与组件通信可预测的状态变更性能优化与重…

Fetch与Axios:区别、联系、优缺点及使用差异

Fetch与Axios&#xff1a;区别、联系、优缺点及使用差异 文章目录 Fetch与Axios&#xff1a;区别、联系、优缺点及使用差异一、联系二、区别1. 浏览器支持与兼容性2. 响应处理3. 请求拦截和响应拦截4. 错误处理 三、优缺点1. Fetch API优点缺点 2. Axios优点缺点 四、使用上的差…

【Docker】快速入门与项目部署实战

我们在部署一个项目时&#xff0c;会出现一系列问题比如&#xff1a; 命令太多了&#xff0c;记不住软件安装包名字复杂&#xff0c;不知道去哪里找安装和部署步骤复杂&#xff0c;容易出错 其实上述问题不仅仅是新手&#xff0c;即便是运维在安装、部署的时候一样会觉得麻烦…

Java面试题尚硅谷版第1季

1、写出如下代码运行结果 1.1、 使用局部变量表和操作数栈解题 1.2、使用前置和后置递增解题 2、写一个单例模式 2.1、考察知识点 2.2、单例模式实现 3、类加载和初始化顺序 package classload;public class Father {private int i test();private static int j method();st…

关于Qt阻断样式继承的解决办法

引言 在使用 Qt 开发桌面应用时&#xff0c;借助样式表&#xff08;StyleSheet&#xff09;来统一定义界面风格是非常常见的做法。通常&#xff0c;你会在主程序中通过 qApp->setStyleSheet(...) 或者直接给某个父控件设置样式表&#xff0c;让所有的子控件都采用相同的配色…

鼠标右键添加新建某种文件的方法

场景 我经常用到.emmx&#xff0c;.eddx文件&#xff0c;电脑上装的是wpsX亿图&#xff08;因为有wps会员&#xff09;&#xff0c;没有开亿图会员。 然后问题就是&#xff0c;思维导图和流程图我都能正常开&#xff0c;正常编辑&#xff0c;但鼠标右键没有新建这两个文件的按…

Inxpect安全雷达传感器与控制器:动态检测 + 抗干扰技术重构工业安全防护体系

Inxpect 推出工业安全领域新型智能传感器与控制器&#xff0c;其核心产品为雷达扫描仪&#xff0c;具备动态调整检测区域、抗干扰能力强等特点&#xff0c;可精准检测危险区域人员进入或存在情况&#xff0c;适用于移动机器人等场景。 Inxpect安全雷达传感器核心功能 动态检测…

【AI学习】李广密与阶跃星辰首席科学家张祥雨对谈:多模态发展的历史和未来

仔细阅读了文章《专访张祥雨&#xff1a;多模态推理和自主学习是未来的 2 个 「GPT-4」 时刻》 https://mp.weixin.qq.com/s/892QuRPH9uP6zN6dS-HZMw 非常赞叹的一篇文章&#xff0c;说清楚了NLP、CV发展中的许多重大问题&#xff0c;读来醍醐灌顶&#xff01;这样的文章&…

C++中std::deque详解和实战工程代码示例

C中std::deque详解和实战工程代码示例 std::deque&#xff08;双端队列&#xff09;是 C 标准库中的一个序列容器&#xff0c;与 std::vector 类似&#xff0c;但它支持从头部和尾部高效地插入和删除元素。它底层采用分段连续空间实现&#xff0c;兼具灵活性与性能。 一、基本…

【AI大模型入门指南】概念与专有名词详解 (二)

【AI大模型入门指南】概念与专有名词详解 &#xff08;二&#xff09; 一 、前言 当你和聊天机器人聊得天花乱坠时&#xff0c;当你用文字让AI生成精美图片时&#xff0c;当手机相册自动帮你分类照片时 —— 这些看似智能的操作背后&#xff0c;都藏着 AI 大模型的身影。 本…

AIStor 的模型上下文协议 (MCP) 服务器:管理功能

在本系列的上一篇博文中&#xff0c;我们讨论了 MinIO AIStor 的模型上下文协议 (MCP) 服务器的基本用户级功能。我们学习了如何使用人类语言命令查看存储桶的内容、分析对象并标记它们以便将来处理&#xff0c;以及如何通过 LLM&#xff08;例如 Anthropic Claude&#xff09;…

期权末日轮实值期权盈利未平仓怎么办?

本文主要介绍期权末日轮实值期权盈利未平仓怎么办&#xff1f;期权末日轮实值期权盈利未平仓该怎么办&#xff0c;需要明确几个关键点&#xff1a;末日轮指的是期权到期日临近的时候&#xff0c;通常指最后一周&#xff0c;尤其是最后一天&#xff0c;这时候时间价值衰减很快&a…

C++/Qt 联合编程中的定时器使用陷阱:QObject::startTimer 报错详解

在 Qt 开发中&#xff0c;QTimer 是一个常用的工具类&#xff0c;用于处理定时事件。但不少开发者在 C/Qt 联合编程&#xff0c;尤其是在工具类、静态类、线程中使用定时器时&#xff0c;会遇到如下令人困惑的报错&#xff1a; QObject::startTimer: Timers can only be used …

CentOS7.9 查询运维安全日志,排查恶意用户

1、查看系统版本 cat /etc/redhat-release uname -a 2、查看所有账号 cat /etc/shadow 3、修改 root 密码 passwd 3、查看账号ID id jinzhi 4、查看登录日志 lastlog 5、查看操作日志 cat .bash_history sudo cat /home/yunwei/.bash_history sudo grep root /va…

多模态大语言模型arxiv论文略读(117)

Training-free Zero-shot Composed Image Retrieval via Weighted Modality Fusion and Similarity ➡️ 论文标题&#xff1a;Training-free Zero-shot Composed Image Retrieval via Weighted Modality Fusion and Similarity ➡️ 论文作者&#xff1a;Ren-Di Wu, Yu-Yen L…

如何正确的配置eureka server集群

将 Eureka Server 实例的 hostname 都配置成相同的值&#xff0c;在 Eureka Server 集群环境下同样是不推荐且通常会导致严重问题的&#xff0c; 核心问题&#xff1a;Eureka Server 集群的工作机制 Eureka Server 集群通过相互注册&#xff08;Peering&#xff09;来实现高可…

AI支持下的-ArcGIS数据处理、空间分析、可视化及多案例综合应用

查看原文>>> 从入门到精通-AI支持下的-ArcGIS数据处理、空间分析、可视化及多案例综合应用 结合ArcGIS和GPT的优势&#xff0c;本文重点进行AI大模型应用、ArcGIS工作流程及功能、Prompt使用技巧、AI助力工作流程、AI助力数据读取与处理、AI助力空间分析、AI助力遥感…

vue3-ts: v-model 和 props 的关系

在 Vue.js 中&#xff0c;v-model 是一个语法糖&#xff0c;它实际上是 :value 和 input 事件的组合。 当你使用 v-model 绑定一个组件时&#xff0c;默认情况下&#xff0c;组件会通过 props 接收 value 这个 prop&#xff0c; 并通过触发 input 事件来更新父组件中的数据。 …