【Linux庖丁解牛】—自定义shell的编写!

news2025/6/6 22:11:41

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 1024
 10 #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 Prompt
 26 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     //如果用户什么都没有输入则返回false
 49     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的子进程,所以我们可以到父进程中获取环境变量!

 

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

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

相关文章

Linux运维笔记:1010实验室电脑资源规范使用指南

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

【Docker 从入门到实战全攻略(二):核心概念 + 命令详解 + 部署案例】

5. Docker Compose Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件来配置应用服务&#xff0c;然后使用一个命令即可创建并启动所有服务。 基本命令 docker-compose up # 创建并启动所有服务 docker-compose down # 停止并移除容器、网络等…

【conda配置深度学习环境】

好的&#xff01;我们从头开始配置一个基于Conda的虚拟环境&#xff0c;覆盖深度学习&#xff08;如PyTorch&#xff09;和传统机器学习&#xff08;如XGBoost&#xff09;&#xff0c;并适配你的显卡&#xff08;假设为NVIDIA&#xff0c;若为AMD请告知&#xff09;。以下是完…

力扣4.寻找两个正序数组的中位数

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

【相机基础知识与物体检测】更新中

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

【前端】性能优化和分类

本页知识点参考&#xff1a;https://zhuanlan.zhihu.com/p/514222781 1. 加载性能优化 1.1 网站性能优化 content方法&#xff1a; 1&#xff09;减少HTTP请求&#xff1a;合并文件&#xff0c;CSS精灵&#xff0c;inline Image 2&#xff09;减少DNS查询&#xff1a;DNS缓存&…

PPO和GRPO算法

verl 是现在非常火的 rl 框架&#xff0c;而且已经支持了多个 rl 算法&#xff08;ppo、grpo 等等&#xff09;。 过去对 rl 的理解很粗浅&#xff08;只知道有好多个角色&#xff0c;有的更新权重&#xff0c;有的不更新&#xff09;&#xff0c;也曾硬着头皮看了一些论文和知…

rk3588 上运行smolvlm-realtime-webcam,将视频转为文字描述

smolvlm-realtime-webcam 是一个开源项目&#xff0c;结合了轻量级多模态模型 SmolVLM 和本地推理引擎 llama.cpp&#xff0c;能够在本地实时处理摄像头视频流&#xff0c;生成自然语言描述&#xff0c; 开源项目地址 https://github.com/ngxson/smolvlm-realtime-webcamhttps…

Rust 学习笔记:Box<T>

Rust 学习笔记&#xff1a;Box Rust 学习笔记&#xff1a;Box<T\>Box\<T> 简介使用 Box\<T\> 在堆上存储数据启用带有 box 的递归类型关于 cons 列表的介绍计算非递归类型的大小使用 Box\<T\> 获取大小已知的递归类型 Rust 学习笔记&#xff1a;Box<…

操作系统学习(十三)——Linux

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

NLP学习路线图(二十二): 循环神经网络(RNN)

在自然语言处理&#xff08;NLP&#xff09;的广阔天地中&#xff0c;序列数据是绝对的核心——无论是流淌的文本、连续的语音还是跳跃的时间序列&#xff0c;都蕴含着前后紧密关联的信息。传统神经网络如同面对一幅打散的拼图&#xff0c;无法理解词语间的顺序关系&#xff0c…

每日一C(1)C语言的内存分布

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

Photoshop使用钢笔绘制图形

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

应用层协议:HTTP

目录 HTTP&#xff1a;超文本传输协议 1.1 HTTP报文 1.1.1 请求报文 1.1.2 响应报文 1.2 HTTP请求过程和原理 1.2.1 请求过程 1、域名&#xff08;DNS&#xff09;解析 2、建立TCP连接&#xff08;三次握手&#xff09; 3、发送HTTP请求 4、服务器处理请求 5、返回H…

复习——C++

1、scanf和scanf_s区别 2、取地址&#xff0c;输出 char ba; char* p&b; cout<<*p; cout<<p; p(char*)"abc"; cout<<*p; cout<<p; cout<<(void*)p; 取地址&#xff0c;把b的地址给p 输出*p&#xff0c;是输出p的空间内的值…

SPI通信协议(软件SPI读取W25Q64)

SPI通信协议 文章目录 SPI通信协议1.SPI通信2.SPI硬件和软件规定2.1SPI硬件电路2.2移位示意图2.3SPI基本时序单元2.3.1起始和终止条件2.3.2交换一个字节&#xff08;模式1&#xff09; 2.4SPI波形分析&#xff08;辅助理解&#xff09;2.4.1发送指令2.4.2指定地址写2.4.3指定地…

JavaWeb:前后端分离开发-部门管理

今日内容 前后端分离开发 准备工作 页面布局 整体布局-头部布局 Container 布局容器 左侧布局 资料\04. 基础文件\layout/index.vue <script setup lang"ts"></script><template><div class"common-layout"><el-containe…

字节开源FlowGram:AI时代可视化工作流新利器

字节终于开源“扣子”同款引擎了&#xff01;FlowGram&#xff1a;AI 时代的可视化工作流利器 字节FlowGram创新性地融合图神经网络与多模态交互技术&#xff0c;构建了支持动态拓扑重构的可视化流程引擎。该系统通过引入 f ( G ) ( V ′ &#xff0c; E ′ ) f(\mathcal{G})…

(LeetCode 每日一题)3403. 从盒子中找出字典序最大的字符串 I (贪心+枚举)

题目&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 题目&#xff1a;贪心枚举字符串&#xff0c;时间复杂度0(n)。 最优解的长度一定是在[1,n-numFriends]之间。 字符串在前缀都相同的情况下&#xff0c;长度越长越大。 C版本&#xff1a; class Solution { public:st…