【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,需要循环以下过程:
- 获取命令行
- 解析命令行
- 建立一个子进程(fork),防止打扰主程序的运行
- 替换子进程(execvp),来执行对应功能。
- 父进程等待子进程退出(wait)
根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了
2 项目实现
为了保证项目文件的优雅美观,我们按照功能来书写不同函数:
- 创建自己的命令行
- 获取命令
- 分割命令
- 创建进程执行命令
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 2569 //获取用户名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( 代表 “ ” )
-
第一次调用strtok(),传入的参数str是要被分割的字符串{aaa - bbb -ccc},而成功后返回的是第一个子字符串{aaa};
-
第二次调用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 25611 #define SkipPath(p) do{ p += strlen(p)-1 ;while(*p != '/') p--; }while(0); 12 #define ZERO '\0'13 #define NUM 3214 #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 else80 {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♪(・ω・)ノ谢谢阅读!!!
下一篇文章见!!!
相关文章:
【Linux】进程实践项目 —— 自主shell编写
送给大家一句话: 不管前方的路有多苦,只要走的方向正确,不管多么崎岖不平,都比站在原地更接近幸福。 —— 宫崎骏《千与千寻》 自主shell命令编写 1 前言2 项目实现2.1 创建命令行2.2 获取命令2.3 分割命令2.4 运行命令 3 源代码…...
基于SpringBoot和Vue的学生笔记共享平台的设计与实现
今天要和大家聊的是一款基于SpringBoot和Vue的学生笔记共享平台的设计与实现 !!! 有需要的小伙伴可以通过文章末尾名片咨询我哦!!! 💕💕作者:李同学 💕&…...
C++心决之命名空间、重载函数和引用
目录 1. C关键字(C98) 2. 命名空间 2.1 命名空间定义 2.2 命名空间使用 3. C输入&输出 4. 缺省参数 4.1 缺省参数概念 4.2 缺省参数分类 5. 函数重载 5.1 函数重载概念 5.2 C支持函数重载的原理--名字修饰(name Mangling) 6. 引用 6.1 引用概念 6.2 引用特性…...
higress使用了解
higress使用了解 了解下 http-echo、httpbin 镜像使用 未按文档实际搭建,但大致是这样 官方文档:https://higress.io/zh-cn/docs/overview/what-is-higress 了解:利用sealos快速安装kubernetes集群:https://note.youdao.com/s…...
Swagger3探索之游龙入海
引言 后端开发中常用的接口调用工具一般使用Postman、ApiPost工具,但后期需要与前端联调,要补充接口文档花费大量时间,此时Swagger3应运而生,大大提高沟通交流的效率。 引用依赖 <!-- Swagger3 调用方式 http://ip:port/swa…...
javaWeb项目-学生考勤管理系统功能介绍
项目关键技术 开发工具:IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架:ssm、Springboot 前端:Vue、ElementUI 关键技术:springboot、SSM、vue、MYSQL、MAVEN 数据库工具:Navicat、SQLyog 1、JAVA技术 JavaSc…...
云备份项目认识、环境搭建以及所使用的库的介绍
一、云备份认识 将本地计算机一个受监管的文件夹的文件上传到服务器中,有服务器组织,客户端可以通过网页将文件查看并且下载下来,下载过程支持断点续传功能,并且服务器会对上传的文件进行热点管理,长时间没人访问的文…...
汇编语言第四版-王爽第2章 寄存器
二进制左移四位,相当于四进制左移一位。 debug命令实操,win11不能启动,需要配置文件 Windows64位系统进入debug模式_window10系统64位怎么使用debugger-CSDN博客...
MoonBit MeetUp回顾——张正、宗喆:编程语言在云原生与区块链领域的技术探索
宗喆和张正分别给我们带了 KCL 相关的最新进展,由蚂蚁集团开发的 Rust 编写的开源 DSL,目标是优化云原生策略配置和用户体验。它通过引入动态配置管理、配置校验和基础设施抽象等核心概念,解决开发者认知负担、配置膨胀和标准化工具缺乏的问题…...
云原生靶场kebernetesGoat、Metarget
靶场 文章目录 靶场kebernetesGoat靶场安装Docker in DockerSSRF漏洞容器逃逸到主系统Docker CIS 基线分析Kubernetes CIS 安全基线分析分析被部署挖矿软件的容器镜像获取环境信息Hidden in layersRBAC最低权限配置错误使用 Sysdig Falco 进行运行时安全监控和检测 Metarget ke…...
【3D目标检测】Det3d—SE-SSD模型训练(前篇):KITTI数据集训练
SE-SSD模型训练 1 基于Det3d搭建SE-SSD环境2 自定义数据准备2.1 自定义数据集标注2.2 训练数据生成2.3 数据集分割 3 训练KITTI数据集3.1 数据准备3.2 配置修改3.3 模型训练 1 基于Det3d搭建SE-SSD环境 Det3D环境搭建参考:【3D目标检测】环境搭建(OpenP…...
k8s1.28.8版本安装prometheus并持久化数据
本文参考 [k8s安装prometheus并持久化数据_/prometheus-config-reloader:-CSDN博客](https://blog.csdn.net/vic_qxz/article/details/119598466)前置要求: 已经部署了NFS或者其他存储的K8s集群. 这里注意networkpolicies网络策略问题,可以后面删除这个策略&#x…...
Mybatis-特殊SQL的执行
1. 模糊查询 在MyBatis中进行模糊查询时,有以下三种常见的实现方式: 1.1. 错误示范 先来个准备操作,并做一个错误示例 根据姓名,模糊查询用户,(x小x) 更新数据表 SQLMapper.java package com.sakurapaid.mybatis3…...
金融衍生品市场
金融衍生品市场 衍生金融品的作用衍生金融工具远期合约期货合约期权 衍生金融品的作用 套期保值(Hedging) 组合多头头寸(long position)与空头头寸(short position)例:股票与股指期货 投机 衍生金融工具 远期合约 定义:在将来…...
2、Cocos Creator 下载安装
Cocos Creator 从 v2.3.2 开始接入了全新的 Dashboard 系统,能够同时对多版本引擎和项目进行统一升级和管理!Cocos Dashboard 将做为 Creator 各引擎统一的下载器和启动入口,方便升级和管理多个版本的 Creator。还集成了统一的项目管理及创建…...
Docker版本:18.06.1安装
1、操作系统:CentOS 7.5以上 2、Docker版本:18.06.1 1、解压 tar -xvf docker-18.06.1-ce.tgz2、将解压出来的docker文件内容移动到 /usr/bin/ 目录下 cp docker/* /usr/bin/3、将docker注册为service vim /etc/systemd/system/docker.service将下列…...
记 SpringBoot 使用@RequestBody 接收不到参数
POST请求,前端传的参数名字跟后端规定的参数一样。但是通过RequestBody注解接收的参数始终为NULL! //实体类中属性没有用驼峰命名 private String SubscribeID; /*** 标题*/ private String Title;解决方案: 1、字段上使用JsonProperty(valu…...
unity 打包安卓错误汇集
Failed to find target with hash string "android-34’ in: D:Pr 他说找不到sdk34level的我用as打开后卸载又重装,最后解决了 我放到Plugins/Android/下面的Java代码没有被编译 这个不知道为什么。我故意把代码写的有问题,会报错那种ÿ…...
C语言-文件操作
🌈很高兴可以来阅读我的博客!🌟我热衷于分享🖊学习经验,🏫多彩生活,精彩足球赛事⚽🔗我的CSDN: Kevin ’ s blog📂专栏收录:C预言 1. 文件的作用 …...
ADB 操作命令详解及用法大全
ADB 简介 ADB,全称 Android Debug Bridge,是 Google 提供的一款用于 Android 平台设备(包括真机和模拟器)调试、交互和管理的命令行工具。通过 ADB,开发者可以在电脑上对连接的 Android 设备执行一系列高级操作&#…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
