初识Linux · 自主Shell编写

news2024/10/4 22:50:45

目录

前言:

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 512
  9 
 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     //child
 93     execvp(gArgv[0],gArgv);
 94     exit(1);
 95   }
 96   else 
 97   {
 98     //father
 99     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编写就差不多了。


感谢阅读!

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

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

相关文章

基于vue框架的大学生四六级学习网站设计与实现i8o8z(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,训练听力,学习单词,单词分类,阅读文章,文章类型,学习课程 开题报告内容 基于Vue框架的大学生四六级学习网站设计与实现开题报告 一、研究背景与意义 随着全球化进程的加速和国际交流的日益频繁&#xff0c;英语作为国际通用语言…

22.3 解读k8s服务发现源码

本节重点介绍 : discovery.Manager服务发现管理员 注册各个服务发现源启动各个服务发现源处理服务发现的结果 k8s服务发现 k8s-client informer机制 架构图补充 注册各个服务发现源 位置 D:\go_path\src\github.com\prometheus\prometheus\discovery\manager.go去掉部分细节…

ConcurrentHashMap 中的并行性

ConcurrentHashMap 在多线程应用程序中被广泛使用。多线程应用程序的示例包括在线游戏应用程序、聊天应用程序&#xff0c;它为应用程序增加了并发性的好处。为了使应用程序本质上更具并发性&#xff0c;ConcurrentHashMap 引入了一个名为“并行性”的概念。 在本文中&#xf…

飞机导航数据库资料

以上是从网上收集的飞机导航数据库的一些资料。现在放在百度网盘中。 链接&#xff1a;https://pan.baidu.com/s/1fDYuaB0DuyKmYt6C_lXvZQ?pwdkcqj 提取码&#xff1a;kcqj

ZTE RRC重建优化案例

ZTE RRC重建优化案例 随着移动通信网络的不断发展&#xff0c;用户对网络的稳定性和覆盖质量提出了更高的要求。尤其在LTE网络中&#xff0c;RRC&#xff08;Radio Resource Control&#xff09;连接的稳定性直接影响用户体验和业务连续性。然而&#xff0c;在实际网络环境中&a…

案例-表白墙简单实现

文章目录 效果展示初始画面提交内容后画面&#xff08;按键按下&#xff09; 代码区 效果展示 初始画面 提交内容后画面&#xff08;按键按下&#xff09; 代码区 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">…

C++输⼊输出

1.<iostream> 是 Input Output Stream 的缩写&#xff0c;是标准的输⼊、输出流库&#xff0c;定义了标准的输⼊、输 出对象 2.std::cin 是 istream 类的对象&#xff0c;它主要⾯向窄字符&#xff08;narrow characters (of type char)&#xff09;的标准输 ⼊流。 3…

STL之priority_queue篇——深入剖析C++中优先队列的实现原理、核心特性及其底层机制

文章目录 前言一、补充内容&#xff1a;堆1.1 什么是堆1.2 堆的分类与性质1.3 堆的向下调整算法&#xff08;小根堆&#xff09;实现流程&#xff1a;代码&#xff1a; 1.4 堆的向上调整算法&#xff08;小根堆&#xff09;实现流程&#xff1a;代码&#xff1a; 1.5 数组建堆算…

eclpsexxx

Copyright?2001-2004 International Business Machines Corp. Guidelines Eclipse 用户界面指南 2.1 版 查看目录 作者&#xff1a;Nick Edgar, Kevin Haaland, Jin Li , Kimberley Peter 译者&#xff1a;Bobbie Wang&#xff0c;Qi Liang 最新更新: 2004年2月 注意 您…

kaggle实战2信用卡反欺诈逻辑回归模型案例1

信用卡欺诈案例 数据集下载地址 https://storage.googleapis.com/download.tensorflow.org/data/creditcard.csv 参考不平衡数据的分类 文章目录 只进行特征衍生&#xff0c;未进行数据标准化、上才样处理数据不平衡问题&#xff0c;得到的准确率和召回率居然很高如果不处理数据…

李宏毅 X 苹果书 自注意力机制 学习笔记上

self attention 是一种network架构使用场景&#xff1a;输入一组向量&#xff0c;这组向量的性质&#xff1a;数量有变化&#xff0c;序列长度不一 模型输入 文字处理&#xff1a; 模型输入&#xff1a;句子&#xff08;句子的长度&#xff0c;单词都不一样&#xff09;&am…

qt QMainWindow 自定义标题栏

可以使用setMenuWidget 来将自定义的标题栏 QWidget 设置进去就可以&#xff0c; 用来替代setMenu 菜单栏单一&#xff0c;自定义不高的问题

node_exporter使用textfile collector收集业务数据

上一篇文章讲了使用Pushgateway收集业务数据的方法&#xff0c;今天讲另外一种方式textfile collector The textfile collector is similar to the Pushgateway, in that it allows exporting of statistics from batch jobs. The Pushgateway should be used for service-leve…

解决ModuleNotFoundError: No module named ‘torchcrf‘

运行深度学习程序时候&#xff0c;出现报错&#xff1a;ModuleNotFoundError: No module named torchcrf 将 from torchcrf import CRF 改为 from TorchCRF import CRF

无设计器简单实例

目录 1、界面设计Qt5元对象系统1. **QObject 类**2. **QMetaObject**3. **信号和槽机制**4. **宏&#xff1a;Q_OBJECT**5. **动态属性**6. **反射机制**7. **元对象编译器&#xff08;MOC&#xff09;** 2、完成程序功能 1、界面设计 不点创建界面 在dialog.h中 #ifndef DIA…

树莓派5里使用protobuf

由于现在protobuf越来越复杂了&#xff0c;自己去编译&#xff0c;还是比较麻烦。 比如最新的V28版本&#xff0c;就会要求使用cmake或者bazel来编译了。 如果不要求使用最新的版本&#xff0c;直接使用系统里带的版本也是可以的。 可以进行如下操作&#xff1a; sudo apt …

【算法系列-链表】交换链表节点(反转 + 交换)

【算法系列-链表】交换链表节点(反转 交换) 文章目录 【算法系列-链表】交换链表节点(反转 交换)1. 反转链表1.1 思路分析&#x1f3af;1.2 解题过程&#x1f3ac;1.3 代码示例&#x1f330; 2. 两两交换链表中的节点2.1 思路分析&#x1f3af;2.2 解题过程&#x1f3ac;2.3 …

电器自动化入门08:隔离变压器、行程开关介绍及选型

视频链接&#xff1a;3.4 电工知识&#xff1a;三相交流异步电动机自动往返行程控制及控制变压器选型_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1PJ41117PW?p8&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.隔离&#xff08;控制&#xff09;变压器 2.行程开…

C++_智能指针详解

什么是智能指针&#xff1f;为什么要有智能指针&#xff1f;到目前为止&#xff0c;我们编写的程序所使用的对象都有着严格定义的生命周期。比如说&#xff0c;全局对象在程序启动时分配&#xff0c;在程序结束时销毁&#xff1b;再比如说局部static对象在第一次使用前分配&…

4.5章节python中的break和continue语句的作用

在Python中&#xff0c;break 和 continue 是两个用于控制循环流程的关键字。它们提供了在特定条件下提前退出循环或跳过当前迭代并进入下一次迭代的机制。 一、break语句 break 语句用于立即终止当前的循环&#xff08;无论是 for 循环还是 while 循环&#xff09;&#xff…