【Linux】进程实践项目 —— 自主shell编写

在这里插入图片描述
送给大家一句话:

不管前方的路有多苦,只要走的方向正确,不管多么崎岖不平,都比站在原地更接近幸福。 —— 宫崎骏《千与千寻》

自主shell命令编写

  • 1 前言
  • 2 项目实现
    • 2.1 创建命令行
    • 2.2 获取命令
    • 2.3 分割命令
    • 2.4 运行命令
  • 3 源代码
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 前言

前几篇文章,我们学习进程的相关知识:进程概念,进程替换,进程控制。熟悉了进程到底是个什么事情,接下来我们来做一个实践,来运用我们所学的相关知识。这个项目就是手搓一个shell模块,模拟实现Xshell中的命令行输入。

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束:
在这里插入图片描述
然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork),防止打扰主程序的运行
  4. 替换子进程(execvp),来执行对应功能。
  5. 父进程等待子进程退出(wait)

根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了

2 项目实现

为了保证项目文件的优雅美观,我们按照功能来书写不同函数:

  1. 创建自己的命令行
  2. 获取命令
  3. 分割命令
  4. 创建进程执行命令

2.1 创建命令行

该模块我们需要实现类似:
在这里插入图片描述
获取这些信息大家应该都知道吧!通过对环境变量我们就可以获取到这些信息。使用getenv()函数就可以完成操作。

#include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/wait.h>
  4 #include<stdlib.h>
  5 #include<unistd.h>
  6 #include<string.h>
  7 //大小宏
  8 #define SIZE 256
  9 //获取用户名
 10 const char* GetUsername() 
 11 {
 12   const char* name = getenv("USER");
 13   if(name == NULL) return "NONE";
 14   return name; 
 15 }
 16 //获取机器信息
 17 const char* GetHostName()
 18 {
 19   const char* hostname = getenv("HOSTNAME");
 20   return hostname; 
 21 }
 22 //获取当前目录
 23 const char* GetCwd()
 24 {
 25   const char* cwd = getenv("PWD");
 26   if(cwd == NULL) return "NONE";
 27   return cwd;
 28 }
 29 
 30 void MakeCommandLineAndPrint()
 31 { //设置命令行字符串
 32   char line[SIZE];
 33   const char* username = GetUsername();
 34   const char* hostname = GetHostName();
 35   const char* cwd = GetCwd();
 	  //将获取的三个数据写入命令行中 
 36   sprintf(line,"[%s@%s %s]> ",username,hostname,cwd);                                                                                                                         
 37   printf("%s",line);
 38   fflush(stdout);//为了将命令行刷新出来
 39 }
 40 
 41 int main()
 42 {
 43   //创建我们自己的命令行
 44   MakeCommandLineAndPrint();
 45   int a = 0; scanf("%d",&a); //阻断一下方便查看
 46   return 0;
 47 }

这里使用的sprintf()函数是向流中写入格式化信息的好工具。这一段函数大家都可以看明白,就是获取三个变量,然后通过Line数组进行中转,然后打印出来。来看效果:
在这里插入图片描述
这时候发现,我们的所在目录全部都别打印出来了,我们可以进行一下优化:

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

通过这个宏定义就可以只保留最后的目录。
这里之所以不使用函数,是因为使用函数会涉及二级指针,会比较复杂!!!
来看效果:
在这里插入图片描述
这样就非常完美了!!!

2.2 获取命令

这个模块可以说是非常关键的一步了,只有正确获取了对应命令,我们才好打开新进程来执行命令。

	  #define ZERO '\0'
   45 int GetUserCommand(char* command,int n)
   46 {
   47   if(command == NULL) return -1;
   48   fgets(command,n,stdin);
   49   command[strlen(command) - 1] = ZERO; 
   50   return strlen(command);
   51 }

这样我们就可以获取命令行输入的字符串了。

2.3 分割命令

获取命令之后,我们还需要对输入的一串命令来进行分割,来保证我们可以正常执行命令

   
   
   12 #define SEP " "
   ...
   14 //全局命令 方便操作
   15 char* gArgv[NUM];
   ...
   58 void SplitCommand(char command[] , size_t n)
   59 {
   60   (void)n;
   61   gArgv[0] = strtok(command,SEP);
   62   int index = 1;
   63   // done, 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL, 并且while判断结束
   64   while((gArgv[index++] = strtok(NULL,SEP)));
   65 }    

我们使用来strtok()函数:

char *strtok(char *str, const char *delim)
  • str—要被分解的字符串
  • delim—用作分隔符的字符(可以是一个,也可以是集合)在这里我们使用宏定义SEP( 代表 “ ” )
  1. 第一次调用strtok(),传入的参数str是要被分割的字符串{aaa - bbb -ccc},而成功后返回的是第一个子字符串{aaa};

  2. 第二次调用strtok的时候,传入的参数应该为NULL,这样使该函数默认使用上一次未分割完的字符串继续分割 ,就从上一次分割的位置作为本次分割的起始位置,直到分割结束。(strtok内部会记录相应信息)

这样就成功分割命令,来看效果:
在这里插入图片描述
我们的准备工作做完了,接下来就可以进行最终的操作:创建新进程来执行命令!

2.4 运行命令

运行命令就要使用:

  • 创建子进程
  • 进程替换

这两个加在一起就有了非常牛批的力量,究极POWER!。

   68 //执行命令
   69 void ExecuteCommand()
   70 {//创建子进程
   71   pid_t id = fork();                                                                                                                                                        
   72   if(id == 0)                                                                                                                 
   73   { //进程替换                                                                                                                          
   74     execvp(gArgv[0],gArgv);                                                                                               
   75     exit(errno);                                                                                                              
   76   }                                                                                                                           
   77   else                                                                                                                        
   78   {                                                                                                                           
   79     int status = 0;                                                                                                           
   80     pid_t rid = waitpid(id,&status,0);//进程等待                                                                                        
   81     if(rid > 0)                                                                                                               
   82     { //如果错误打印错误信息                                                                                                                        
   83       int lastcode = WEXITSTATUS(status);                                                                                     
   84       if(lastcode != 0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);                                            
   85     }                                                                                                                              
   86   }                                                                                                                                
   87 }   

前面已经做好大部分工作了,执行命令这一步就很简单了。来看效果:
在这里插入图片描述
这样就完成了绝大部分的代码编写。我们在加上一个while循环,让命令行一直运行试试:
在这里插入图片描述

这样就实现了shell的大部分功能,但是还是有一些功能没有做到:比如我们运行cd等内建命令时,会无法运行,所以我要加上特殊情况来保证内建命令可以执行!!!

90 char* GetHome()
 91 {
 92   char* home = getenv("HOME");
 93   return home;
 94 }
 95 
 96 char cwd[SIZE];
 97 
 98 void cd()
 99 {
100   const char* path = gArgv[1];
101   if(path == NULL) path = GetHome();
102   chdir(path);
103 
104   char temp[SIZE];
105   getcwd(temp,sizeof(temp));                                                                                                                                                  
106   snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
107   putenv(cwd);
108 }
109 
110 //检查是否为内建命令 并单独执行
111 bool CheckBuildin()
112 {
113   bool yes = false;
114   //if语句判断即可,内建命令是有限的
115   if(strcmp(gArgv[0],"cd") == 0)
116   {
117     cd();
118     yes = true;
119   }
120   return yes;
121 }
123 int main()
124 {
125   int quit = 0;                                                                                                                                                               
126 
127   while(!quit)
128   {
129 
130     //创建我们自己的命令行
131     MakeCommandLineAndPrint();
132     
133     //获取命令行信息
134     char usercommand[SIZE];
135     int n = GetUserCommand(usercommand,sizeof(usercommand));
136     if(n <= 0) return 1;
137     
138     //分割命令行信息
139     SplitCommand(usercommand, sizeof(usercommand));
140     
141     bool judge = CheckBuildin();
142     if(judge) continue;
143 
144     //执行命令
145     ExecuteCommand();
146   }
147 
148 
149   return 0;
150 }
       

这样把内建命令单独进行运行就可以了,我这里只写了一个cd命令。来看效果:
在这里插入图片描述
这样就完成了我们的自主shell编写!!!

3 源代码

#include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/wait.h>
  4 #include<stdlib.h>
  5 #include<unistd.h>
  6 #include<string.h>
  7 #include<errno.h>
  8 #include<stdbool.h> 
  9 
 10 #define SIZE 256
 11 #define SkipPath(p) do{ p += strlen(p)-1 ;while(*p != '/') p--; }while(0); 
 12 #define ZERO '\0'
 13 #define NUM 32
 14 #define SEP " "
 15 
 16 //命令
 17 char* gArgv[NUM];
 18 int lastcode = 0;
 19 char cwd[SIZE];
 20 
 21 const char* GetUsername()
 22 {
 23   const char* name = getenv("USER");
 24   if(name == NULL) return "NONE";
 25   return name; 
 26 }
 27 
 28 const char* GetHostName()
 29 {
 30   const char* hostname = getenv("HOSTNAME");
 31   return hostname; 
 32 }
 33 
 34 const char* GetCwd()
 35 {
 36   const char* cwd = getenv("PWD");
 37   if(cwd == NULL) return "NONE";
 38   return cwd;
 39 }
 40                                                                                                                                                                               
 41 void MakeCommandLineAndPrint()
 42 {
 43   char line[SIZE];
 44   const char* username = GetUsername();  
 45   const char* hostname = GetHostName();
 46   const char* cwd = GetCwd();
 47   SkipPath(cwd);
 48   sprintf(line,"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1?"/":cwd + 1);
 49   printf("%s",line);
 50   fflush(stdout);
 51 }                                                                                                                                                                             
 52 
 53 int GetUserCommand(char command[] ,size_t n)
 54 {
 55   char* s = fgets(command,n,stdin);
 56   if(s == NULL) return -1;
 57   command[strlen(command) - 1] = ZERO; 
 58   return strlen(command);
 59 }
 60 
 61 void SplitCommand(char command[] , size_t n)
 62 {
 63   (void)n;
 64   gArgv[0] = strtok(command,SEP);
 65   int index = 1;
 66   // done, 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL, 并且while判断结束
 67   while((gArgv[index++] = strtok(NULL,SEP)));
 68 } 
 69 
 70 //执行命令
 71 void ExecuteCommand()
 72 {
 73   pid_t id = fork();
 74   if(id == 0)
 75   {
 76     execvp(gArgv[0],gArgv);
 77     exit(errno);
 78   }
 79   else
 80   {
 81     int status = 0;
 82     pid_t rid = waitpid(id,&status,0);
 83     if(rid > 0)
 84     {
 85       lastcode = WEXITSTATUS(status);
 86       if(lastcode != 0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);
 87     }
 88   } 
 89 }
 90 
 91 char* GetHome()
 92 {
 93   char* home = getenv("HOME");
 94   return home;
 95 }
 96                                                                                                                                                                               
 97 
 98 void cd()
 99 {
100   const char* path = gArgv[1];
101   if(path == NULL) path = GetHome();
102   chdir(path);
103 
104   char temp[SIZE];
105   getcwd(temp,sizeof(temp));
106   snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
107   putenv(cwd);
108 }
109 
110 //检查是否为内建命令 并单独执行
111 bool CheckBuildin()
112 {
113   bool yes = false;
114   //if语句判断即可,内建命令是有限的
115   if(strcmp(gArgv[0],"cd") == 0)
116   {
117     cd();
118     yes = true;
119   }
120   return yes;
121 }
122 
123 int main()
124 {
125   int quit = 0;
126 
127   while(!quit)
128   {
129 
130     //创建我们自己的命令行
131     MakeCommandLineAndPrint();
132     
133     //获取命令行信息
134     char usercommand[SIZE];
135     int n = GetUserCommand(usercommand,sizeof(usercommand));
136     if(n <= 0) return 1;
137     
138     //分割命令行信息
139     SplitCommand(usercommand, sizeof(usercommand));
140     
141     bool judge = CheckBuildin();
142     if(judge) continue;
143 
144     //执行命令
145     ExecuteCommand();
146   }
147 
148 
149   return 0;
150 }

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机服务器中了rmallox勒索病毒怎么办?rmallox勒索病毒解密数据恢复

网络技术的不断发展与应用&#xff0c;大大提高了企业的生产运营效率&#xff0c;越来越多的企业开始网络开展各项工作业务&#xff0c;网络在为人们提供便利的同时&#xff0c;也会存在潜在威胁。近日&#xff0c;云天数据恢复中心接到多家企业的求助&#xff0c;企业的计算机…

Python内置函数enumerate()

Python的内置函数enumerate()。在学习过程中遇到了一点小问题。记录一下。 enumerate() 是 Python 中常用的内置函数之一&#xff0c;它可以用来同时遍历序列的索引和对应的值。具体来说&#xff0c;enumerate() 接受一个可迭代对象作为参数&#xff0c;返回一个包含索引和值的…

vuees6新语法

vue的学习网站&#xff1a; https://www.runoob.com/vue2/vue-tutorial.html1.Vue的介绍 学习目标 说出什么是Vue能够说出Vue的好处能够说出Vue的特点 内容讲解 【1】Vue介绍 1.vue属于一个前端框架&#xff0c;底层使用原生js编写的。主要用来进行前端和后台服务器之间的…

Holiday Notice

Holiday Notice 放假通知 要是每个公司都能放假放的多&#xff0c;把加班折算放假落实到位&#xff0c;还怕我们不努力干活&#xff0c;巴不得把全年都干完了&#xff0c;然后休息。

HCIP【GRE VPN配置】

目录 实验要求&#xff1a; 实验配置思路&#xff1a; 实验配置过程&#xff1a; 一、按照图式配置所有设备的IP地址 &#xff08;1&#xff09;首先配置每个接口的IP地址 &#xff08;2&#xff09;配置静态路由使公网可通 二、在公网的基础上创建GRE VPN隧道&#xff0…

HarmonyOS实战开发-如何实现一个简单的健康生活应用(上)

介绍 本篇Codelab介绍了如何实现一个简单的健康生活应用&#xff0c;主要功能包括&#xff1a; 用户可以创建最多6个健康生活任务&#xff08;早起&#xff0c;喝水&#xff0c;吃苹果&#xff0c;每日微笑&#xff0c;刷牙&#xff0c;早睡&#xff09;&#xff0c;并设置任…

C++list的模拟实现

为了实现list&#xff0c;我们需要实现三个类 一、List的节点类 template<class T> struct ListNode {ListNode(const T& val T()):_pPre(nullptr),_pNext(nullptr),_val(val){}ListNode<T>* _pPre;ListNode<T>* _pNext;T _val; }; 二、List的迭代器…

2024年腾讯云服务器99元一年_老用户优惠续费不涨价

腾讯云99元一年服务器配置为轻量2核2G4M、50GB SSD盘、300GB月流量、4M带宽&#xff0c;新用户和老用户都可以购买&#xff0c;续费不涨价&#xff0c;续费价格也是99元一年。以往腾讯云优惠服务器都是新用户专享的&#xff0c;这款99元服务器老用户也可以购买&#xff0c;这是…

Spring Task 知识点详解、案例、源代码解析

简介&#xff1a;Spring Task 定时任务   所谓定时任务。就是依据我们设定的时间定时运行任务&#xff0c;就像定时发邮件一样&#xff0c;设定时间到了。邮件就会自己主动发送。 在Spring大行其道的今天&#xff0c;Spring也提供了其定时任务功能&#xff0c;Spring Task。同…

安装dalton过程中出现的pcre问题

在前面文章中&#xff0c;基于多种流量检测引擎识别pcap数据包中的威胁&#xff0c;并没有详细的说明dalton的安装。由于dalton提供了脚本./start-dalton.sh &#xff0c;执行之后会自动的安装各种依赖以及suricata&#xff0c;zeek&#xff0c;snort的容器环境。但是在实际执行…

编程新手必看!从零起步掌握Python的终极指南,Python简介(1)

1、Python语言的诞生 Python的作者&#xff0c;Guido von Rossum&#xff08;吉多范罗苏姆&#xff0c;中国Python程序员都叫他 龟叔&#xff09;&#xff0c;荷兰人。1982年&#xff0c;龟叔从阿姆斯特丹大学获得了数学和计算机硕士学位。然而&#xff0c;尽管他算得上是一位…

内存管理--柔性数组

本次讲的是&#xff0c;柔性数组&#xff0c;如果哪位小博客想要了解的更多&#xff0c;可以登录下面这个网站&#xff0c;了解详细内容 C语言结构体里的成员数组和指针 | 酷 壳 - CoolShellhttps://coolshell.cn/articles/11377.html 我们就听说过数组&#xff0c;听说过柔性数…

Excel求解二元一次方程

背景&#xff1a;如果想求解二元一次方程&#xff0c;常规方法就是联立方程求出一个未知数&#xff0c;然后带入任意一个等式。那么在excel里面应该怎么解决呢&#xff1f; 总所周知&#xff0c;大学里面会学矩阵行列式&#xff0c;二元一次方程其实就是一个简单的矩阵行列式。…

006 高并发内存池_PageCache设计

​&#x1f308;个人主页&#xff1a;Fan_558 &#x1f525; 系列专栏&#xff1a;高并发内存池 &#x1f339;关注我&#x1f4aa;&#x1f3fb;带你学更多知识 文章目录 前言文章重点一、回顾PageCache页缓存结构二、PageCache结构设计三、完善申请内存函数小结 前言 本文将…

聊一聊电子邮件?

电子邮件是什么&#xff1f; 电子邮件是一种基于客户/服务器架构的应用。功能是实现人与人之间的交流。直到现在&#xff0c;电子邮件依然是当前因特网 注意&#xff1a;基于客户/服务器方式和基于B/S架构不一样&#xff01;客户/服务器表示的范围更广&#xff0c;当基于客户…

Linux环境jdk、git、maven、MySQL和redis的安装和配置

这里整理了在Linux上如何安装和配置jdk、git、maven、MySQL和redis&#xff0c;主要用于在Linux上部署Java项目 文章中博主使用了一个叫FinalShell远程连接软件进行上传&#xff0c;如果没有类似的工具也可以直接通过yum命令行下载包 博主使用的Linux服务器为centos7&#xf…

2024最新版Android studio安装入门教程(非常详细)

目录 JDK安装与配置 一、下载JDK 二、JDK安装 三、JDK的环境配置 四、JDK的配置验证 Android studio安装 Android studio连接手机真机调试&#xff08;以华为鸿蒙为例&#xff09; 一、新建一个android项目 二、进入项目面板 三、配置Android Studio 四、安装手机驱…

【JavaSE】初识线程,线程与进程的区别

文章目录 ✍线程是什么&#xff1f;✍线程和进程的区别✍线程的创建1.继承 Thread 类2.实现Runnable接口3.匿名内部类4.匿名内部类创建 Runnable ⼦类对象5.lambda 表达式创建 Runnable ⼦类对象 ✍线程是什么&#xff1f; ⼀个线程就是⼀个 “执行流”. 每个线程之间都可以按…

BigInteger的应用

这里写目录标题 例题BigInteger常用方法关于BigInteger初始化为nullcompareTo()方法 : 返回一个int型数据(1 大于; 0 等于 ; -1 小于) 例题 import java.math.BigInteger; import java.util.*; public class Main{public static void main(String[] args) {BigInteger n BigIn…

android 消息提醒

1.创建 MyBackgroundService.java 继承 Service public class MyBackgroundService extends Service {Overridepublic void onCreate() {super.onCreate();Log.i("业务服务", "开起业务服务");//调用服务后在页面手机上创建一个通知消息。if (android.os…