当前位置: 首页 > 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&…...

谷歌浏览器插件

项目中有时候会用到插件 sync-cookie-extension1.0.0&#xff1a;开发环境同步测试 cookie 至 localhost&#xff0c;便于本地请求服务携带 cookie 参考地址&#xff1a;https://juejin.cn/post/7139354571712757767 里面有源码下载下来&#xff0c;加在到扩展即可使用FeHelp…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...