【Linux庖丁解牛】—自定义shell的编写!
1. 打印命令行提示符
在我们使用系统提供的shell时,每次都会打印出一行字符串,这其实就是命令行提示符,那我们自定义的shell当然也需要这一行字符串。
这一行字符串包含用户名,主机名,当前工作路径,所以,我们在打印这行字符串时,需要获取这些信息。根据我们之前学过的知识,我们可以用getenv系统调用来获取!
这里有一个接口gethostname,我试过了用getenv来获取系统的主机名,但是,在我的系统上似乎无法获取,这可能和系统有关,但是我们用gethostname这个接口也可以很安全的获得主机名,具体用法用man手册看一看也就会了。
结果没有问题:
2. 获取用户输入
当我们解决了命令行提示符的问题后,接下来我们就会注意到每次执行指令都会有一个光标在闪烁等待用户输入指令!所以我们现在就要解决这个问题!
如果用scanf来获取缓冲区的字符串坑定是不行的,因为scanf默认以空格作为分隔符,而我们在输入指令带选项时,就会有空格!
那我们就用fgets:
可是为什么回显时有两次换行呢?
原因含很简单:我们在输入指令时,最后输入的换行也在缓冲区中被fges获取保留在数组commandline的最后一个字符,解决方法也很简单,只需要把最后一个字符置为0即可!
现在写的代码还是不够优雅,我们稍微封装一下:
1 #include <iostream>2 #include <cstdlib>3 #include <cstdio>4 #include <unistd.h>5 #include <cstring>6 7 using namespace std;8 9 #define COMMAND_SIZE 102410 #define FORMAT "%s@%s:%s$ "11 12 const char* get_user_name()13 {14 const char* user=getenv("USER");15 return user==NULL?"NONE":user;16 //return user;17 }18 19 const char* get_pwd()20 {21 const char* pwd=getenv("PWD");22 return pwd==NULL?"NONE":pwd;23 }24 25 //制作命令行提示符Command Prompt26 void make_command_prompt(char cmd_prompt[],int size)27{28 char hostname[256];29 gethostname(hostname,sizeof(hostname));30 snprintf(cmd_prompt,size,FORMAT,get_user_name(),hostname,get_pwd());31 } 32 33 //打印命令行提示符34 void print_cmd_prompt()35 {36 char prompt[COMMAND_SIZE];37 make_command_prompt(prompt,sizeof(prompt));38 printf("%s",prompt);39 fflush(stdout);40 }41 42 //获取用户输入的命令43 bool get_command(char* out,int size)44 {45 char* c=fgets(out,size,stdin);46 if(c==NULL) return false;47 out[strlen(out)-1]=0;48 //如果用户什么都没有输入则返回false49 if(strlen(out)==0) return false;50 return true;51 }52 53 int main()54 {55 //1.打印命令行提示法56 print_cmd_prompt();57 //2.获取用户输入的命令58 char commandline[COMMAND_SIZE];59 if(get_command(commandline,sizeof(commandline)))60 {61 printf("%s\n",commandline);62 }63 return 0;64 }
我们使用的shell是不断在获取用户的指令的,也就是说shell一旦跑起来就是一个死循环,直到我们退出shell!所以我们还应该将我们的主体逻辑改一下!
3. 解析命令行
我们获取了用户输入的字符串后【ls -a -l】,我们不可能用这一长串字符串去执行我们的指令,我们需要做的下一步就是将我们获取的字符串按空格切割!具体如何做到如下:
我们先在全局定义一个命令行参数表char* g_argv[MAXARGC]来记录我们切割的命令行参数
接下来,我们封装一个函数来完成我们的切割任务:
测试函数:
测试结果:
4. 执行命令
执行命令也非常简单,这需要用到我们之前学过的知识,创建子进程,将子进程进行程序替换!
5.简化工作路径的显示
通过上图我们可以观察到我们自定义的shell显示的工作路径太长了,为了和原shell尽可能保持一致,所以我们封装一个函数来解决这个问题!
6. 检测并处理内建命令
我们在输入ls,pwd等命令时,我们自定义的shell雀氏可以很好的帮我们完成工作。但是,当我们输入cd,export等命令时,此时的shell就不再适用了。cd命令是改变当前的工作路径,但是我们自定义的shell是子进程通过进程替换的方式帮我们执行命令,而cd这类命令是去环境变量表中那到当前的工作路径,我们需要更改父进程bash的环境变量。所以对于cd这类的命令,我们需要用父进程去执行。而cd这类的命令我们又称为内建命令,因此,在执行命令之前,我们需要一个检测并处理内建命令的操作!
下面是测试结果:
我们发现工作路径果然发生改变了,但是命令行显示的路径为什么没有发生改变呢?
但cd命令执行时,先是进程的工作路径发生改变,然后环境变量中记录的工作路径再改变,而这个工作也是由shell来完成的,但是目前我们的自定义shell还没有实现这个功能!并且,我们获取当前工作路径是通过获取环境变量的方式拿到的,所以我们在命令行中显示的工作路径永远是久的!
因此,获取当前工作路径有一个更好的方式->系统调用【getcwd】!
下面的测试就符合预期了!
但是,环境变量中的pwd是实实在在发生了变化的,所以我们自定义的shell也应该实现这一个功能!
所以,我们仅需要在获取当前工作路径之后,用puenv导入到环境变量中即可!
当然,还有许多内建命令,比如echo,我们可以完善这些内建命令,这里就不写了【比较懒】。
7. 完善环境变量表
目前这里自定义的shell只有命令行参数表,还缺少一张环境变量表。父进程bash在启动时,从配置文件中获取环境变量,子进程则继承父进程的环境变量。如果我们要模拟bash获取环境变量的方式,就必须从配置文件中那数据。但是,这里目前是做不到的【没办法到配置文件中拿数据】。
不过,我们自定义的shell本质上还是bash的子进程,所以我们可以到父进程中获取环境变量!
相关文章:

【Linux庖丁解牛】—自定义shell的编写!
1. 打印命令行提示符 在我们使用系统提供的shell时,每次都会打印出一行字符串,这其实就是命令行提示符,那我们自定义的shell当然也需要这一行字符串。 这一行字符串包含用户名,主机名,当前工作路径,所以&a…...
C++抽象类与多态实战解析
这段 C 代码演示了 抽象类(Abstract Class) 和 多态(Polymorphism) 的使用,它定义了一个表示教师的抽象基类 Teacher,并派生出两个具体的子类:EnglishTeacher(英语老师)和…...
OpenAI API 流式传输
OpenAI API 流式传输教程 🌊 本教程将详细解释 OpenAI API 如何进行数据流式传输,从基本的文本块到复杂的工具调用指令。流式传输允许你逐步从模型接收数据,这对于构建响应灵敏的用户界面和处理长输出非常有用。 1. 基础知识:Ser…...
嵌入式分析利器:DuckDB与SqlSugar实战
一、DuckDB 的核心特性与适用场景 DuckDB 是一款 嵌入式分析型数据库(OLAP) ,专为高效查询设计,主要特点包括: 列式存储与向量化引擎 数据按列存储,提升聚合统计效率(如 SUM/AVG…...
嵌入式学习笔记 - freeRTOS任务设计要点
一 中断函数中不允许操作任务 因为中断函数使用的上下文环境是MSP环境,而非PSP环境,不允许挂起任务,不允许阻塞任务的任何操作。 可以使用FromISR函数进行操作。 二 中断的频率与处理时间 中断的处理时间要远低于任务的运行时间ÿ…...

Linux运维笔记:1010实验室电脑资源规范使用指南
文章目录 一. 检查资源使用情况,避免冲突1. 检查在线用户2. 检查 CPU 使用情况3. 检查 GPU 使用情况4. 协作建议 二. 备份重要文件和数据三. 定期清理硬盘空间四. 退出 ThinLinc 时注销,释放内存五. 校外使用时配置 VPN注意事项 总结 实验室的电脑配备了…...

12:点云处理—调平,角度,平面度,高度,体积
1.调平 2.夹角、平面度 3.高度、体积...
Marketo 集成 8x8 Connect 短信 API 指南
一、🔍 项目背景与目标 在营销自动化流程中,需要在用户完成特定行为(如填写表单、完成注册)后,自动发送一条短信进行提醒、欢迎或验证。 Marketo 原生不具备短信发送能力,但支持通过 Webhook 集成第三方 A…...

【Docker 从入门到实战全攻略(二):核心概念 + 命令详解 + 部署案例】
5. Docker Compose Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件来配置应用服务,然后使用一个命令即可创建并启动所有服务。 基本命令 docker-compose up # 创建并启动所有服务 docker-compose down # 停止并移除容器、网络等…...
Elasticsearch索引(Index)介绍,它与数据库中的表有什么区别?
在Elasticsearch(ES)中,索引(Index)是存储和组织文档(Document)的逻辑容器,类似于关系型数据库(如MySQL)中的“数据库(Database)”或“表(Table)”,但设计理念和实现机制有显著差异。以下从定义、核心特性、与数据库表的对比三方面详细解析。 一、索引的定义与…...
Elasticsearch中什么是分析器(Analyzer)?它由哪些组件组成?
在Elasticsearch(ES)中,分析器(Analyzer)是处理文本的核心组件,负责将原始文本转换为适合索引和搜索的词项(Term)。它直接影响搜索的准确性和性能,是构建高效搜索系统的关键。 一、分析器的核心作用 1. 分词(Tokenization):将文本拆分为独立的词(Token)。 例如…...
使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
1.普通文本消息的发送和接收 GetMapping("/stream")public SseEmitter streamResponse() {SseEmitter emitter new SseEmitter(0L); // 0L 表示永不超时Executors.newSingleThreadExecutor().execute(() -> {try {for (int i 1; i < 5; i) {emitter.send(&q…...
.net Avalonia 在centos部署
.NET Avalonia 在 CentOS 部署指南 在跨平台应用开发中,.NET Avalonia 凭借其强大的功能和灵活性受到了广泛关注。而将基于 .NET Avalonia 开发的应用程序部署到 CentOS 系统上,是很多开发者会面临的任务。下面就为大家详细介绍在 CentOS 上部署 .NET A…...
MyBatis深度解析:XML/注解配置与动态SQL编写实战
引言 在现代Java企业级应用开发中,MyBatis作为一款优秀的持久层框架,因其灵活性和易用性广受开发者喜爱。相比Hibernate等全自动ORM框架,MyBatis提供了更接近SQL的开发体验,同时又不失面向对象的优雅。本文将深入探讨MyBatis的核…...
面试经验 对常用 LLM 工具链(如 LlamaFactory)的熟悉程度和实践经验
面试场景: 你正在面试一个大型语言模型(LLM)工程师或研究员的职位,面试官想了解你对常用 LLM 工具链(如 LlamaFactory)的熟悉程度和实践经验。 面试经验分享:LlamaFactory-CLI 工具实践 面试官…...

【conda配置深度学习环境】
好的!我们从头开始配置一个基于Conda的虚拟环境,覆盖深度学习(如PyTorch)和传统机器学习(如XGBoost),并适配你的显卡(假设为NVIDIA,若为AMD请告知)。以下是完…...

力扣4.寻找两个正序数组的中位数
文章目录 题目介绍题解 题目介绍 题解 题解链接:题解 核心思路:通过二分查找的确定分割点使左右两部分元素数量相等。 class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int n1 nums1.length;int n2 nums2.length…...

【相机基础知识与物体检测】更新中
参考: 黑马机器人 | 相机标定&物体检测https://robot.czxy.com/docs/camera/ 01-相机基础 相机基础概述 相机是机器视觉的基础,相机直接产生了相机数据。所有视觉算法都是作用在相机数据上的。相机数据的好坏,或者对相机数据的理解方式…...

【前端】性能优化和分类
本页知识点参考:https://zhuanlan.zhihu.com/p/514222781 1. 加载性能优化 1.1 网站性能优化 content方法: 1)减少HTTP请求:合并文件,CSS精灵,inline Image 2)减少DNS查询:DNS缓存&…...

PPO和GRPO算法
verl 是现在非常火的 rl 框架,而且已经支持了多个 rl 算法(ppo、grpo 等等)。 过去对 rl 的理解很粗浅(只知道有好多个角色,有的更新权重,有的不更新),也曾硬着头皮看了一些论文和知…...
ceph 对象存储用户限额满导致无法上传文件
查看日志 kl logs -f rook-ceph-rgw-my-store-a-5cc4c4d5b5-26n6j|grep -i error|head -1Defaulted container "rgw" out of: rgw, log-collector, chown-container-data-dir (init) debug 2025-05-30T19:44:11.573+0000 7fa7b7a6d700...

rk3588 上运行smolvlm-realtime-webcam,将视频转为文字描述
smolvlm-realtime-webcam 是一个开源项目,结合了轻量级多模态模型 SmolVLM 和本地推理引擎 llama.cpp,能够在本地实时处理摄像头视频流,生成自然语言描述, 开源项目地址 https://github.com/ngxson/smolvlm-realtime-webcamhttps…...
某航参数逆向及设备指纹分析
文章目录 1. 写在前面2. 接口分析3. 加密分析4. 算法还原5. 设备指纹风控分析与绕过【🏠作者主页】:吴秋霖 【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究…...
SQL思路解析:窗口滑动的应用
目录 🎯 问题目标 第一步:从数据中我们能直接得到什么? 第二步:我们想要的“7天窗口”长什么样? 第三步:SQL 怎么表达“某一天的前六天”? 🔍JOIN 比窗口函数更灵活 第四步&am…...

Rust 学习笔记:Box<T>
Rust 学习笔记:Box Rust 学习笔记:Box<T\>Box\<T> 简介使用 Box\<T\> 在堆上存储数据启用带有 box 的递归类型关于 cons 列表的介绍计算非递归类型的大小使用 Box\<T\> 获取大小已知的递归类型 Rust 学习笔记:Box<…...
C# 从 ConcurrentDictionary 中取出并移除第一个元素
C# 从 ConcurrentDictionary 中取出并移除第一个元素 要从 ConcurrentDictionary<byte, int> 中取出并移除第一个元素,需要结合 遍历 和 原子移除操作。由于 ConcurrentDictionary 是无序集合,"第一个元素" 通常是指最早添加的元素&…...

操作系统学习(十三)——Linux
一、Linux Linux 是一种类 Unix 的自由开源操作系统内核,由芬兰人 Linus Torvalds 于 1991 年首次发布。如今它广泛应用于服务器、桌面、嵌入式设备、移动设备(如 Android)等领域。 设计思想: 原则描述模块化与可移植性Linux 内…...

NLP学习路线图(二十二): 循环神经网络(RNN)
在自然语言处理(NLP)的广阔天地中,序列数据是绝对的核心——无论是流淌的文本、连续的语音还是跳跃的时间序列,都蕴含着前后紧密关联的信息。传统神经网络如同面对一幅打散的拼图,无法理解词语间的顺序关系,…...

每日一C(1)C语言的内存分布
目录 代码区 常量区 全局/静态区 初始化数据段(.data) 未初始化数据段(.bss) 堆区 栈区 总结 今天我们学习的是C语言的内存分布,以及这些分区所存储的内容和其特点。今天的思维导图如下。 C语言作为一款直接处…...

Photoshop使用钢笔绘制图形
1、绘制脸部路径 选择钢笔工具,再选择“路径”。 基于两个点绘制一个弯曲的曲线 使用Alt键移动单个点,该点决定了后续的曲线方向 继续绘制第3个点 最后一个点首尾是同一个点,使用钢笔保证是闭合回路。 以同样的方式绘制2个眼睛外框。 使用椭…...