初识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编写就差不多了。


感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据提取之JSON与JsonPATH

第一章 json 一、json简介 json简单说就是javascript中的对象和数组&#xff0c;所以这两种结构就是对象和数组两种结构&#xff0c;通过这两种结构可以表示各种复杂的结构 > 1. 对象&#xff1a;对象在js中表示为{ }括起来的内容&#xff0c;数据结构为 { key&#xff1…

区块链+Web3学习笔记(METAMASHK、密码学知识)

学习资料来源于B站&#xff1a; 17小时最全Web3教程&#xff1a;ERC20&#xff0c;NFT&#xff0c;Hardhat&#xff0c;CCIP跨链_哔哩哔哩_bilibili 该课程提供的Github代码地址&#xff0c;相关资料详见README.md&#xff1a; Web3_tutorial_Chinese/README.md at main sm…

银河麒麟系统内存清理

银河麒麟系统内存清理 1、操作步骤2、注意事项 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 当银河麒麟系统运行较长时间&#xff0c;内存中的缓存可能会积累过多&#xff0c;影响系统性能。此时&#xff0c;你可以通过简单的命令来清理这…

JS | 如何解决ajax无法后退的问题?

Ajax请求通常不支持浏览器的后退按钮&#xff0c;因为它们是异步的&#xff0c;不会导致页面重新加载(刷新)。但如果你想要用户能够通过浏览器的后退按钮回到之前的页面状态&#xff0c;你可以通过几种方法来解决这个问题&#xff1a; 1、使用pushState和replaceState方法 hi…

【Android】数据存储

本章介绍Android五种主要存储方式的用法&#xff0c;包括共享参数SharedPreferences、数据库SQLite、SD卡文件、App的全局内存&#xff0c;另外介绍重要组件之一的应用Application的基本概念与常见用法&#xff0c;以及四大组件之一的内容提供器ContentProvider的基本概念与常见…

五.海量数据实时分析-FlinkCDC+DorisConnector实现数据的全量增量同步

前言 前面四篇文字都在学习Doris的理论知识&#xff0c;也是比较枯燥&#xff0c;当然Doris的理论知识还很多&#xff0c;我们后面慢慢学&#xff0c;本篇文章我们尝试使用SpringBoot来整合Doris完成基本的CRUD。 由于 Doris 高度兼容 Mysql 协议&#xff0c;两者在 SQL 语法…

Redis数据库与GO(二):list,set

一、list&#xff08;列表&#xff09; list&#xff08;列表&#xff09;是简单的字符串列表&#xff0c;按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。List本质是个链表&#xff0c; list是一个双向链表&#xff0c;其元素是有序的&#xff0c;元…

GS-SLAM论文阅读笔记-CaRtGS

前言 这篇文章看起来有点像Photo-slam的续作&#xff0c;行文格式和图片类型很接近&#xff0c;而且貌似是出自同一所学校的&#xff0c;所以推测可能是Photo-slam的优化与改进方法&#xff0c;接下来具体看看改进了哪些地方。 文章目录 前言1.背景介绍GS-SLAM方法总结 2.关键…

uniapp+Android面向网络学习的时间管理工具软件 微信小程序

目录 项目介绍支持以下技术栈&#xff1a;具体实现截图HBuilderXuniappmysql数据库与主流编程语言java类核心代码部分展示登录的业务流程的顺序是&#xff1a;数据库设计性能分析操作可行性技术可行性系统安全性数据完整性软件测试详细视频演示源码获取方式 项目介绍 用户功能…

STM32F103C8----3-3 蜂鸣器(跟着江科大学STM32)

一&#xff0c;电路图 &#xff08;接线图&#xff09; 面包板的的使用请参考&#xff1a;《面包板的使用_面包板的详细使用方法-CSDN博客》 二&#xff0c;目的/效果 3-3 蜂鸣器 三&#xff0c;创建Keil项目 详细参考&#xff1a;《STM32F103C8----2-1 Keil5搭建STM32项目模…

Linux ssh 免密登录配置

参考资料 ~/.ssh/configについて~/.ssh/configを使ってSSH接続を楽にする.ssh/configファイルでSSH接続を管理する 目录 一. 密钥生成1.1 生成工具1.1.1 OpenSSH1.1.2 Git 1.2 生成命令1.3 注意事项1.4 解决路径中的用户名乱码 二. 将公钥配置到目标服务&#xff0c;免密登录2…

Spring Boot集成encache快速入门Demo

1.什么是encache EhCache 是一个纯 Java 的进程内缓存框架&#xff0c;具有快速、精干等特点&#xff0c;是 Hibernate 中默认的 CacheProvider。 Ehcache 特性 优点 快速、简单支持多种缓存策略&#xff1a;LRU、LFU、FIFO 淘汰算法缓存数据有两级&#xff1a;内存和磁盘&a…

Linux bash脚本 远程开发环境配置

参考资料 太香了&#xff0c;VSCode远程开发插件&#xff0c;值得一试Visual Studio Code で Remote SSH する。Managing extensions 目录 一. 远程开发必备二. 连接远程开发服务器三. 安装远程开发插件 一. 远程开发必备 ⏹ VSCode插件 Remote - SSH 通过使用 SSH 链接虚拟…

C++之多态篇(超详细版)

1.多态概念 多态就是多种形态&#xff0c;表示去完成某个行为时&#xff0c;当不同的人去完成时会有不同的形态&#xff0c;举个例子在车站买票&#xff0c;可以分为学生票&#xff0c;普通票&#xff0c;军人票&#xff0c;每种票的价格是不一样的&#xff0c;当你是不同的身…

如何高效删除 MySQL 日志表中的历史数据?实战指南

在处理高并发的物联网平台或者其他日志密集型应用时&#xff0c;数据库中的日志表往往会迅速增长&#xff0c;数据量庞大到数百GB甚至更高&#xff0c;严重影响数据库性能。如何有效管理这些庞大的日志数据&#xff0c;特别是在不影响在线业务的情况下&#xff0c;成为了一项技…

使用Windows远程桌面连接Linux

要在Kali Linux上使用Windows远程桌面连接&#xff08;MSTSC.exe&#xff09;&#xff0c;你可以通过配置xrdp服务来实现。以下是在Kali Linux上设置xrdp以便Windows远程桌面连接的具体步骤&#xff1a; 一、安装xrdp和Xfce桌面环境 更新软件包列表&#xff1a; 打开终端&…

Python和C++混淆矩阵地理学医学物理学视觉语言模型和算法模型评估工具

&#x1f3af;要点 优化损失函数评估指标海岸线检测算法评估遥感视觉表征和文本增强乳腺癌预测模型算法液体中闪烁光和切伦科夫光分离多标签分类任务性能评估有向无环图、多路径标记和非强制叶节点预测二元分类评估特征归因可信性评估马修斯相关系数对比其他准确度 Python桑…

基于C++和Python的进程线程CPU使用率监控工具

文章目录 0. 概述1. 数据可视化示例2. 设计思路2.1 系统架构2.2 设计优势 3. 流程图3.1 C录制程序3.2 Python解析脚本 4. 数据结构说明4.1 CpuUsageData 结构体 5. C录制代码解析5.1 主要模块5.2 关键函数5.2.1 CpuUsageMonitor::Run()5.2.2 CpuUsageMonitor::ComputeCpuUsage(…

大数据技术:Hadoop、Spark与Flink的框架演进

大数据技术&#xff0c;特别是Hadoop、Spark与Flink的框架演进&#xff0c;是过去二十年中信息技术领域最引人注目的发展之一。这些技术不仅改变了数据处理的方式&#xff0c;而且还推动了对数据驱动决策和智能化的需求。在大数据处理领域&#xff0c;选择合适的大数据平台是确…

有些硬盘录像机接入视频汇聚平台EasyCVR后通道不显示/显示不全,该如何处理?

EasyCVR视频监控汇聚管理平台是一款针对大中型项目设计的跨区域网络化视频监控集中管理平台。该平台不仅具备视频资源管理、设备管理、用户管理、运维管理和安全管理等功能&#xff0c;还支持多种主流标准协议&#xff0c;如GB28181、RTSP/Onvif、RTMP、部标JT808、GA/T 1400协…