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

初识Linux · 自主Shell编写

目录

前言:

1 命令行解释器部分

2 获取用户命令行参数

3 命令行参数进行分割

4 执行命令

5 判断命令是否为内建命令


前言:

本文介绍是自主Shell编写,对于shell,即外壳解释程序,我们目前接触到的命令行解释器,有bash,还有SSH,对于今天模拟实现的Shell编写,我们模拟的是bash,以及需要的预备知识前文已经介绍了,进程的多方面的知识,在自主Shell编写里面比较重要的是进程程序替换,进程终止,进程等待,进程状态什么的,都是自主Shell编写里面的辅助知识罢了。

那么,话不多说,我们直接进入到Shell编写部分。


1 命令行解释器部分

我们在Centos版本下进行演示,首先,我们平常看到的命令行解释器,呈现的都是这个模样,最开始的_lazy是当前的用户名,@后面的VM-12-14-centos代表的是当前主机名称,后面的~代表的我们所处的当前目录,那么我们这里,就应该要复刻一个一样的出来。

那么第一个问题来了,我们从哪里获取对应的用户名主机名以及目前的目录呢?

此时,前文引进的环境变量,就应该出场了:

输入了env之后,我们可以在环境变量表里面看到许多对应的环境变量,其中HOSTNAME,PWD,USER分别代表的就是主机名称,当前路径,当前用户名。

那么我们如何通过获取?我们已知的是有3种方式,一种是environ,一种是命令行参数表,一种是getenv。

我们这里使用getenv,相对于二级指针environ,getenv是我们最常见的选择,那么我们可以:

   11   char* argv[] = {12   getenv("HOSTNAME"),13   getenv("USER"),14   getenv("PWD")15   };

将获取到的环境变量放在数组argv里面,随即进行打印:

我们直接使用printf打印数组的三个元素,看起来好像没有问题,因为命令行参数是在后面输入,所以我们不能使用\n作为结束,并且,这里介绍一个函数,snprintf,我们不妨使用该函数打印,把所有的环境变量放在一个字符串里面,似乎更好控制一点,这里如果有同学的man手册配置没有齐全的话,可以使用指令:

sudo yum install man-pages

snprintf就是将所有的输出,放到一个字符串里面,此时,我们直接打印该字符即可,所以第一部分的临时代码为:

 34 void OutputBash()  35 {  36   char line[SIZE];  37     38   char* username = GetUser();                                                              39   char* hostname = Gethost();  40   char* cwd = Getcwd();41 42   snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,cwd);43   printf("%s",line);44   fflush(stdout);45 46   // char* argv[] = {47   // getenv("HOSTNAME"),48   // getenv("USER"),49   // getenv("PWD")50   // };51   // char* line;52   // //printf("[%s@%s %s]>",argv[0],argv[1],argv[2]);53   // fflush(stdout);54 }
  8 #define SIZE 5129 10 11 char* GetUser()12 {13   char* user = getenv("USER");14   if(user == NULL) return NULL;15   return user;16 17 }18 19 char* Gethost()                                                    20 {                                                                  21   char* host = getenv("HOSTNAME");                                 22   if(host == NULL) return NULL;                                    23   return host;                                                     24 }                                                                  25                                                                    26 char* Getcwd()                                                     27 {                                                                  28   char* cwd = getenv("PWD");                                       29   if(cwd == NULL) return NULL;                                     30   return cwd;  31   32 }  

但是为什么要说这是临时的呢?因为我们的pwd并不完善:

目前,打印的出来并不是最完善的,较为完善的应该是只打印当前目录。

那么如何保证修饰一下呢?

我们可以将该字符串进行分割,也就是使用指针,将该指针的指向指到最后一个/指向的地方即可。但是这里不推荐使用函数,如果使用的是函数,我们就要使用二级指针,实属麻烦,所以可以使用宏即可:

#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)

那么判断的条件就是,只要p碰到了根目录就停下,但是有个缺陷就是:

/还是存在,那么我们可以这样操作:

 snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd + 1); 

此时,较为完善的命令行解释器部分就打印出来了:


2 获取用户命令行参数

第一个问题我们解决了,我们现在该获取用户的命令行参数了。

在获取用户命令行参数这里,我们要注意的点是,我们应该使用什么函数来获取?

可不可以使用scanf来获取呢?如果使用scanf,那么ls -l -n -a,能获取到多少呢?

我们知道scanf是通过空格或者换行符来获取的,此时ls -l -n -a,就只能获取到ls,所以我们应该换个函数,这里推荐fgets,其实gets也是可以的,但是因为后面有文件的IO操作,所以我们使用fgets作为一个缓冲:

 57 int GetUserCommand(char* usercommand,size_t n)  58 {  59   char* s = fgets(usercommand,n,stdin);  60   if(s == NULL) return -1;  61   62   return strlen(s);  63 }  

但是该代码存在一定的缺陷。

在第4部分会有提示。


3 命令行参数进行分割

获取到了对应的命令,那么执行的时候,不能带空格去执行吧?所以我们要使用函数,将命令行参数进行分割,这里使用的函数是C语言的库函数,strtok,相信许多同学已经忘记了,不急:

第一个参数是分割的字符串,第二个参数是分割符,那么第一次分割之后,将第一个参数置为NULL,就会继续分割,我们要做的,就是将字符串分割之后,放到数组里面,有益于后面的进程替换工作。

这里定义一个全局变量,用于存在分割后的字符串变量:

#define SEP " "   char* gArgv[SIZE];

这里有一个非常细小的地方,如果我们使用单引号的空格,虽然也是空格,但是和strtok就不匹配了,因为这并不是cosnt char* ,这只是一个字符而已。

 70 void SplitCommand(char* usercommand)71 {72   gArgv[0] = strtok(usercommand,SEP);73   int index = 1;74   while((gArgv[index++] = strtok(NULL,SEP)));//分割之后函数返回NULL 恰好作为结尾75 76 }

此时有个很不错的代码细节,因为函数分割完返回的就是NULL,刚好可以作为数组的结束标志。


4 执行命令

到现在,我们可以不管三七二十一,直接执行命令了,至少我们现在先不用管命令是不是内建命令,我们就执行几个简单的即可。

那么要执行命令,我们肯定涉及到进程程序替换。因为分割好的命令我们已经放在了全局变量里面,所以我们可以直接创建函数了:

 85 void ExcuteCommand()86 {87   pid_t id = fork();88 89   if(id < 0) Die();90   else if(id == 0)91   {92     //child93     execvp(gArgv[0],gArgv);94     exit(1);95   }96   else 97   {98     //father99     int status = 0;
100     pid_t rid = waitpid(id,&status,0);
101     if(rid > 0)
102     {
103       lastcode = WEXITSTATUS(status);
104       if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
105     }
106   }
107 
108 
109 }

这些代码都是进程替换的时候介绍过的了,无非是加修饰,让代码更加美观,此时,咱们就可以跑了,但是有同学仍会发现,不管怎么运行,都是不可以的,因为我们命令行输入的时候,都会自动的输入一个回车,这个回车,导致了我们跑不了,所以我们需要将回车干掉:

宏定义ZERO即可。

此时,我们就可以正常的执行了。


5 判断命令是否为内建命令

那么现在问题来了,如果我们是执行的ehco,cd这种内建命令,即只能父进程来执行的,我们就不能创建子进程了,判断是否为内建命令,条件成立就内建执行即可,并且跳过下一步:

那么判断内建命令的方式也是十分简单粗暴的,strcmp即可:

110 void Cd()
111 {
112     const char *path = gArgv[1];
113     if(path == NULL) path = Gethost();
114     // path 一定存在
115     chdir(path);
116 
117     // 刷新环境变量
118     char temp[SIZE*2];
119     getcwd(temp, sizeof(temp));
120     snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
121     putenv(cwd); // OK
122 }
123 
124 int IsInorder()
125 {
126     int yes = 0;
127     const char *enter_cmd = gArgv[0];
128     if(strcmp(enter_cmd, "cd") == 0)
129     {
130         yes = 1;
131         Cd();
132     }
133     else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)  
134     {  
135         yes = 1;  
136         printf("%d\n", lastcode);  
137         lastcode = 0;                                                                              
138     }  
139     return yes;  
140 }

这里拿cd举例子,判断cd是内建命令之后,在cd函数实现,因为我们要该目录,所以使用函数chdir,改变当前工作目录,改变了之后,改变环境变量中的PATH即可。此时自主shell编写就差不多了。


感谢阅读!

相关文章:

初识Linux · 自主Shell编写

目录 前言&#xff1a; 1 命令行解释器部分 2 获取用户命令行参数 3 命令行参数进行分割 4 执行命令 5 判断命令是否为内建命令 前言&#xff1a; 本文介绍是自主Shell编写&#xff0c;对于shell&#xff0c;即外壳解释程序&#xff0c;我们目前接触到的命令行解释器&am…...

海报设计模板免费的好用吗?活动海报排版技巧轻松get

虽然今年所有的法定节假日已经过完了&#xff0c;但对于电商打工族来说&#xff0c;需要制作活动海报的节日&#xff0c;还有很多吧...... 刚听到小道消息&#xff0c;说是今年的双十一&#xff0c;从十月十四号就开始预热了&#xff01; 怎么样&#xff0c;大家的预热活动海…...

【Linux基础】03 Linux环境基础开发工具使用

1. yum ——软件包管理器 yum 是我们 Linux 预装的一个指令&#xff0c;搜索、下载、、安装对应的软件 yum 相当于 Linux 的应用商店&#xff01; 安装与卸载 yum list | grep command 通过 yum list 命令可以罗列出当前一共有哪些软件包. 由于包的数目可能非常之多, 这里我…...

【CSS】flex: 1; 的意思

在 Flexbox 布局中&#xff0c;flex: 1; 是一个简写属性&#xff0c;它表示弹性容器中的子元素如何分配可用空间。flex: 1 意味着该元素可以根据剩余的空间进行扩展&#xff0c;占据相应的比例。具体来说&#xff0c;flex: 1; 是 flex-grow、flex-shrink 和 flex-basis 这三个属…...

C++ 3D冒险游戏开发案例

3D冒险游戏的C开发案例&#xff0c;包括游戏设计、实现细节、图形渲染、音效处理等内容。 3D冒险游戏开发案例 一、游戏设计 游戏概述 游戏名称&#xff1a;“探索者的传奇”类型&#xff1a;3D冒险游戏目标&#xff1a;玩家控制角色在一个开放的世界中探索、解谜、战斗并完成…...

【AIGC】Exa AI 要做 AI 领域的 Google

又一个AI搜索引擎诞生&#xff1a;Exa AI。 与其他旨在取代谷歌的AI驱动搜索引擎不同&#xff0c;Exa的目标是创建一个专门为AI设计的搜索工具。 Exa的使命: 互联网包含人类的集体知识&#xff0c;但目前的搜索体验更像在垃圾场中导航&#xff0c;而非在知识图书馆中漫游。核…...

YOLOv8 基于MGD的知识蒸馏

YOLOv8 基于MGD的知识蒸馏 接着上一篇我们介绍了YOLOv8的剪枝方案和代码&#xff0c;本篇文章将剪枝后的模型作为学生模型&#xff0c;剪枝前的模型作为教师模型对剪枝模型进行蒸馏&#xff0c;从而进一步提到轻量模型的性能。 Channel-wise Distillation (CWD) 问题和方法 …...

全国消防知识竞赛活动方案哪家强

关键词&#xff1a;消防安全、预防火灾、消防意识、消防员、防火安全 适合行业&#xff1a;所有行业 推荐功能&#xff1a;答题、投票、H5 宣传角度 1.从日常生活场景出发&#xff0c;指导大家如何检查家庭中的火灾隐患。例如检查电线是否老化、插座是否过载、是否在楼梯间…...

JavaEE学习一条龙服务————概述

鉴于之前的笔记较乱&#xff0c;没有逻辑关系&#xff0c;&#xff0c;博主决定从JacaEE整个学习的阶段出发&#xff0c;整理一系列博客&#xff0c;供大家学习交流&#xff0c;提升自己。 此文章已绑定一篇我为大家梳理的JavaEE一条龙学习知识点的文档&#xff0c;大家可下载…...

分支预测器BPU

分支预测器BPU 0 Intro0.1 CPU执行过程0.2 分支预测0.2.1 TAGE预测器0.2.2 跳转地址 分支预测器BPU是深入研究一个高性能处理器的一个很好的开始项目&#xff1b; 0 Intro 条件分支是指后续具有两路可执行的分支。可以分为跳转分支(taken branch)和不跳转分支(not-taken branc…...

Go 系列教程 —— 数组和切片

数组 数组是同一类型元素的集合。例如&#xff0c;整数集合 5,8,9,79,76 形成一个数组。Go 语言中不允许混合不同类型的元素&#xff0c;例如包含字符串和整数的数组。&#xff08;译者注&#xff1a;当然&#xff0c;如果是 interface{} 类型数组&#xff0c;可以包含任意类型…...

适配器模式【对象适配器模式和类适配器模式,以及具体使用场景】

2.1-适配器模式 ​ 类的适配器模式是把适配者类的API转换成为目标类的API&#xff0c;适配器模式使得原来由于接口不兼容而不能一起工作的那些类可以一起工作&#xff0c;其实在具体的开发中&#xff0c;对于自己系统一开始的设计不会优先考虑适配器模式&#xff0c;通常会将接…...

【EXCEL数据处理】保姆级教程 000016案例 EXCEL的vlookup函数。

【EXCEL数据处理】000016案例 vlookup函数。 前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】保姆级教…...

【软件推荐】通过Rufus制作信创操作系统U盘安装盘 _ 统信 _ 麒麟 _ 方德

原文链接&#xff1a;【软件推荐】通过Rufus制作信创操作系统U盘安装盘 | 统信 | 麒麟 | 方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于如何使用Rufus制作信创操作系统&#xff08;如统信UOS、麒麟KOS、中科方德等&#xff09;的U盘启动安装盘的文章。Ruf…...

MySql 多表设计

项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系&#xff0c;基本分为&#xff1a;一对多&#xff0c;多对多&a…...

wpf实现新用户页面引导

第一步 第二部 部分代码: private void show(int xh, FrameworkElement fe, string con, Visibility vis Visibility.Visible) {Point point fe.TransformToAncestor(Window.GetWindow(fe)).Transform(new Point(0, 0));//获取控件坐标点RectangleGeometry rg new Rectangl…...

【小白向】机器人入门之ROS系统的学习(Ubuntu24.04+ROS2)

目录 一.复杂的机器人系统 二.ROS机器人系统 1.简介 1.节点 2.话题 2.安装 3.测试 4.可视化 RQT&#xff1a; RVIZ&#xff1a; 显示属性&#xff1a; 显示状态&#xff1a; 一.复杂的机器人系统 依照我们现在的技术来看&#xff0c;机器人系统仍是极其复杂的&#xff0c;往…...

SNAP-MS策略:可溶性水凝胶微珠,高效表征蛋白质复合物

大家好&#xff01;今天来了解一种高效的蛋白质复合物纯化和表征策略的文章——《Biofunctionalized dissolvable hydrogel microbeads enable efficient characterization of native protein complexes》发表于《Nature Communications》。蛋白质复合物在生命过程中起着关键作…...

java对象序列化Serializable的应用场景

目录 Java对象序列化的应用场景 网络通信&#xff1a; 对象持久化&#xff1a; 分布式计算&#xff1a; 缓存存储&#xff1a; 远程方法调用&#xff08;RMI&#xff09;&#xff1a; 基于JMS的消息传递&#xff1a; Java集合类中的对象需要被存储&#xff1a; 对象深…...

springboot-网站开发-linux服务器部署jar格式图片存档路径问题

springboot-网站开发-linux服务器部署jar格式图片存档路径问题&#xff01;近期在部署自己的网站源码&#xff0c;使用的是jar格式的编码格式。发布到远程服务器后&#xff0c;发现客户捐款的证书图片存在异常。 经过排查代码&#xff0c;找到了原因。下面分享给大家。 1&…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分&#xff1a; 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...