【Linux】实战小项目-----Bash的简易版

news2025/1/10 21:13:39

目录

一、什么是Bash

二、实现Bash:

1、整体需求分析:

2、初始化:

3、分割字符串:

4、执行普通命令:

5、内建命令与特殊处理:

        1、ls的颜色:

2、内建命令cd:

3、export:

4、echo

三、源码:


一、什么是Bash

首先理解shell,Bash,命令行解释器在Linux中扮演的角色:

Shell‌:用于解释和执行用户的命令,提供用户与操作系统之间的交互接口
Bash‌:作为Shell的一种实现,提供了更多的功能和增强特性,是大多数Linux发行版的默认Shell‌命令行解释器‌:负责解释和执行用户输入的命令,是Shell的核心功能之一

Bash也是一个进程,并且是不断运行中的进程,那么在我们输入指令的时候实际上就是Bash创建了子进程,然后子进程进行进程替换为要执行的进程,进而完成我们的命令,

当我们执行一个指令进程之后,可以看到这个进程的父进程是Bash,证明了所有指令都是Bash的子进程

二、实现Bash:

1、整体需求分析:

Bash的任务:就是把一个指令进程进行解释,然后进行进程替换

所以整体思路:

1、实现命令行的外表
2、把用户输入指令的看做一个字符串
3、把这个字符串进行分解
4、分解后创建子进程进行进程替换
5、对特殊的指令(内建命令)进行特殊处理

看看主函数的整体逻辑:

首先就是初始化,这是拿到输入的指令
接着分割字符串,如果没有输入就continue重新读取输入
然后执行内建命令,如果执行成功返回1,没有执行则返回0
最后执行普通命令

2、初始化:

对于这两个可以封装为一个函数

上面,传过来的这个line是一个数组,作用是读取命令行中的一整行字符串,

printf后面通过自己定义的宏(这样个性化的时候就只需修改宏即可)和自己所写的函数来完成命令行外表

接着定义一个char* 类型来在输入流中拿到字符串放在target数组中

最后一行是处理回车符将读取的回车变为 \0 这样就能够得到最干净的字符串了

3、分割字符串:

然后就是对字符串进行分割

如上,这里是用strtok函数进行分割的,具体解析可参考下述文章

strtok()函数详解! - Yuxi001 - 博客园icon-default.png?t=O83Ahttps://www.cnblogs.com/yuxiyuxi/p/17770807.html

如上采用的是使用函数strtok进行分割,DEL是定义的宏,使用其的原因同样是方便修改,总体思路很简单,首先在全局里定义一个数组(这里是char* argv[ ])来放字符串,所以每次分割后都放到argv中,最后返回 i - 1 也就是切割后的子字符串的个数方便后续维护

4、执行普通命令:

前面了解到执行普通命令就是Bash创建子进程然后进行进程替换进而完成

思路:
封装一个函数,在函数中首先fork创建子进程,
在子进程代码块中使用函数execvp进行进程替换,第一个参数就是分割后的argv中的0下标位置,第二个参数就是argv数组的所有,直接传数组名即可,然后如果替换失败就结束进程,退出码设置为自己所设置的特殊退出码
在父进程代码块中等待子进程即可

这样就可以执行普通指令了

5、内建命令与特殊处理:

        1、ls的颜色:

实际上就是在指令ls后面加上 --color 即可,所以可以在内建命令中进行一次特殊处理,即可实现ls的颜色显示

思路:
通过strcmp函数进行判断,发现如果是ls指令的话就在此时的argv数组中的argc位置加上--color,然后把argc++,之后注意把argv的argc位置置为NULL

2、内建命令cd:

为什么cd命令需要进行特殊处理呢?

这是因为Bash已经创建子进程了,然后子进程进行cd到其他目录下,这个时候就虽然确实加载了cd命令,但是是不会影响父进程的,和父进程就没有啥关系了,所以就需要父进程自己去处理,

这里采用的是chdir函数进行内建命令cd的处理:

如下,当修改成功时,返回0,修改失败时返回 -1

实现思路:
在这里的Bash实现,cd命令后面必须跟上一个字符串,然后也是使用strcmp函数继续比较
在实现中直接先使用chdir()函数,里面的参数就是argv数组的第二个,
然后下面的getpwd()是之前写的一个函数,作用是得到当前工作路径下的绝对路径,这样就能够修改环境变量中的PWD了
最后在把环境变量的PWD修改,这里使用sprintf()函数实现,将格式化的数据写入PWD环境变量中

3、export:

同cd命令一样,如果正常替换程序在使用export函数只是修改了子进程的环境变量,就不会影响父进程了,所以就需要对export函数继续特殊处理,

处理思路:
首先依然是用strcmp函数进行判断后进入命令为export的代码块中,在处理的过程中不能简单的认为直接使用putenv导入环境变量

这是因为环境变量表是一个字符指针数组,每个指针指向对应的环境变量字符串,所以当简单地putenv的时候是在这个字符指针数组中找一个没有被使用的地方,指向_argv[1]的,这样就会导致下一次输指令的时候就会覆盖掉被_argv[1]的位置,这样尽管环境变量表中还是指向原来导入的环境变量位置的,但是由于这个位置被其他指令覆盖了,所以每次导入环境变量后再输入别的指令,这个环境变量就没了(这就是类似于浅拷贝)

解决方法:

在堆上开辟一块空间或者定义一个二维数组维护(从易变区到不变区方便维护)
把堆上的空间或者二维数组和_argv[1]处待添加的环境变量交换
然后putenv读取新开辟的空间添加到环境变量表

(其实就是类似于深拷贝的处理过程)

这样输入指令后就能够在环境变量中看到了,并且不会被覆盖

4、echo

echo指令一般情况下是正常打印字符串,但是如果添加了$符号那么就证明是要打印环境变量之类的,所以也需要进行特殊处理:

如上,在进入echo指令区域后进行判断,如果是$?就打印退出码,这个退出码在普通命令中已经得到了如下:

在打印过后就把退出码重新赋值为0表示正常退出,

然后在判断,如果_argv[1]位置处的第一个字符是$就证明需要打印环境变量,此时getenv获得环境变量后打印出来即可,

最后一个else就正常打印即可

如果需要增加其他功能,继续添加else if即可

三、源码:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>

#define LEFT "["
#define RIGHT "]"
#define LAB "#"
#define MAX_SIZE 1024
#define DEL " \t"
#define ARGC 32
#define EXIT_CODE 1


char target[MAX_SIZE];//拿去命令行中的一整行字符串
char* argv[ARGC];//把字符串分解后放入该数组
int last_normal_code = 0;//退出码
char pwd[MAX_SIZE];

const char* getusrname()
{
	return getenv("USER");
}

const char* gethost()
{
	return getenv("HOME");
}

void getpwd()
{
	//获取当前工作目录下的绝对路径
	getcwd(pwd,sizeof(pwd));
}

void Init(char* line)
{
	getpwd();
	printf(LEFT"%s@%s %s"RIGHT""LAB" ",getusrname(),gethost(),pwd);
	char* str = fgets(target,MAX_SIZE,stdin);
	assert(str);//输入的指令不能为空,但是一般是不可能的
	(void)str;//要把str使用一下,在release版本下assert就没有了,如果不使用可能会报错
	//"ls -a -l\n\0"
	line[strlen(line)-1] = '\0';
}

int split(char* line)
{
	int i = 0;
	argv[i++] = strtok(line,DEL);
	while(argv[i++] = strtok(NULL,DEL));
	return i - 1;
}

int InbuildExe(int argc,char* _argv[])
{
	if(argc == 2 && strcmp(_argv[0],"cd") == 0)
	{
		chdir(_argv[1]);
		getpwd();
		sprintf(getenv("PWD"),"%s",pwd);
		return 1;
	}
	else if(argc == 2 && strcmp(_argv[0],"export") == 0)
	{
		char* tmp = (char*)malloc(sizeof(char)*strlen(_argv[1]));
		strcpy(tmp,_argv[1]);
		putenv(tmp);
		return 1;
	}
	else if(argc == 2 && strcmp(_argv[0],"echo") == 0)
	{
		if(strcmp(_argv[1],"$?")==0)
		{
			printf("%d\n",last_normal_code);
			last_normal_code = 0;
		}
		else if(*_argv[1] == '$')
		{
			char* val = getenv(_argv[1]+1);
			if(val)
			printf("%s\n",val);
		}
		else
		{
			printf("%s\n",_argv[1]);
		}
		return 1;
	}

	if(strcmp(_argv[0],"ls") == 0)
	{
		_argv[argc++] = "--color";
		_argv[argc] = NULL;
	}
	return 0;
}

//argv是一个数组,里面的元素是char*,然后用char* _argv[](或者是char** _argv)接收argv的话,是接收的是首元素的地址,
//首元素的地址就有一个*,然后又是char* 类型的有个*,所以就是char** _argv等价于char* argv[]
void NormalExe(char* _argv[])
{
	pid_t id = fork();
	if(id < 0)
	{
		perror("进程创建失败\n");
		return;
	}
	else if(id == 0)
	{
		execvp(_argv[0],_argv);
		exit(EXIT_CODE);
	}
	else 
	{
		int status = 0;
		pid_t ret = waitpid(id,&status,0);
		if(ret == id)
		{
			//printf("等待成功\n");
			last_normal_code = WEXITSTATUS(status);//获取进程的退出码
		}
	}
}

int main()
{
	while(1)
	{
		//初始化
		Init(target);
		
		//分割字符串
		int ret = split(target);
		if(ret == 0) continue;

		//执行内建命令
		int flag = InbuildExe(ret,argv);
		//执行普通命令
		if(!flag) NormalExe(argv);
	}
	return 0;
}

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

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

相关文章

windows文件下换行, linux上不换行 解决CR换行符替换为LF notepad++

html文件是用回车换行的&#xff0c;在windows电脑上&#xff0c;显示正常。 文件上传到linux服务器后&#xff0c;文件不换行了。只有一行。而且相关js插件也没法正常运行。 用notepad查看&#xff0c;显示尾部换行符&#xff0c;是CR&#xff0c;这就是原因。CR是不被识别的。…

汽车产业数字化转型:协同创新破解挑战,平衡安全与流通

在数字经济时代的浪潮中&#xff0c;数据资源和数据信息已成为驱动各行各业转型升级的“新石油”。汽车产业&#xff0c;作为国民经济的重要支柱&#xff0c;正经历着前所未有的变革。随着数字化创新和转型的深入&#xff0c;数据在汽车全产业链中的作用和价值日益凸显。在这个…

C语言程序设计P5-2【应用函数进行程序设计 | 第二节】——知识要点:函数的参数及返回值和函数的调用及声明

知识要点&#xff1a;函数的参数及返回值和函数的调用及声明 视频&#xff1a; 目录 一、任务分析 二、必备知识与理论 三、任务实施 一、任务分析 1.求x的n次方的值也就是求n个x的乘积&#xff0c;可把x和n作为函数的形参&#xff0c;定义一个函数power来完成这个功能&am…

40分钟学 Go 语言高并发:GRPC框架使用

gRPC框架使用 一、gRPC基础知识 知识点说明重要程度应用场景RPC原理远程过程调用的基本原理⭐⭐⭐⭐⭐分布式系统通信协议设计Protocol Buffers的使用⭐⭐⭐⭐⭐接口定义、数据序列化服务定义gRPC服务和方法定义⭐⭐⭐⭐服务接口设计性能优化连接池、压缩、流式处理⭐⭐⭐⭐高…

HTML Input 文件上传功能全解析:从基础到优化

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【ETCD】【源码阅读】ETCD启动流程源码解读

启动流程的图如下&#xff1a; 1、主函数入口 ETCD 启动的入口在 etcd/server/main.go 文件中。 package mainimport ("os""go.etcd.io/etcd/server/v3/etcdmain" )func main() {etcdmain.Main(os.Args) }这里调用了 etcdmain.Main()&#xff0c;这是 …

【RBF SBN READ】hadoop社区基于RBF的SBN READ请求流转

读写分离功能的背景及架构 当前联邦生产集群的各个子集群只有Active NameNode在工作,当读写任务变得繁忙的时候,只有一个Active负责处理的话,此时集群的响应和处理能力业务侧感知会明显下降,为此,我们将引入Observer架构,实现读写功能的分离,使得Active只负责写请求,而…

计算机运行时提示错误弹窗“由于找不到 quazip.dll,无法继续执行代码。”是什么原因?“quazip.dll文件缺失”要怎么解决?

计算机运行时错误解析&#xff1a;解决“quazip.dll缺失”问题指南 在软件开发和日常计算机使用中&#xff0c;我们经常会遇到各种运行时错误。今天&#xff0c;我们将深入探讨一个常见的错误提示&#xff1a;“由于找不到quazip.dll&#xff0c;无法继续执行代码。”这一弹窗…

NuHertz/HFSS: 使用矩形、径向和阻抗短截线的平面 LPF 切比雪夫-II 实现

我们今天的主题是使用 NuHertz 和 HFSS 设计 Microstrip Lowpass Chebyshev-Type2 滤波器。切比雪夫 2 型在通带中具有平坦的响应&#xff0c;在阻带中具有波纹。我们将比较 NuHertz 中的不同选项。 低通滤波器由集总 L 和 C 组件制成。这种方法很难用于高频应用程序。高频滤波…

SpringBoot整合knife4j,以及会遇到的一些bug

这篇文章主要讲解了“Spring Boot集成接口管理工具Knife4j怎么用”&#xff0c;文中的讲解内容简单清晰&#xff0c;易于学习与理解&#xff0c;下面请大家跟着小编的思路慢慢深入&#xff0c;一起来研究和学习“Spring Boot集成接口管理工具Knife4j怎么用”吧&#xff01; 一…

高效的 Java 对象映射库“Orika”

什么是 Orika Orika 是一个高效的 Java 对象映射库&#xff0c;专门用于简化 Java 应用程序中对象之间的转换。它以自动化和优化的方式将一个对象的属性映射到另一个对象&#xff0c;从而减少了手动编写重复代码的需要。Orika 特别适合处理复杂的对象结构以及数据传输对象 &am…

汽车总线协议分析-CAN-FD总线

随着汽车功能的增多&#xff0c;各ECU之间的信息交互也越来越频繁&#xff0c;导致总线负载持续走高&#xff0c;CAN2.0报文只有约40%-50%带宽实际用于数据传输&#xff0c;响应机制易受车内布线的物理特性限制&#xff0c;如广播延迟、导线延迟等&#xff0c;CAN的局限性也逐渐…

【JavaEE】多线程(6)

一、用户态与内核态 【概念】 用户态是指用户程序运行时的状态&#xff0c;在这种状态下&#xff0c;CPU只能执行用户态下的指令&#xff0c;并且只能访问受限的内存空间 内核态是操作系统内核运行时的状态&#xff0c;内核是计算机系统的核心部分&#xff0c;CPU可以执行所有…

故障处理--kuboard无法访问,etcd磁盘空间不足

问题现象&#xff1a; kuboard页面报错 排查过程&#xff1a; 1、查看kuboard是否正常。 2、查看kuboard容器的日志&#xff1a; docker logs -f --tail10 kuboard 大概内容如下&#xff1a; levelerror msg"failed to rotate keys: etcdserver: mvcc: database sp…

unity3d—demo(实现给出图集名字和图片名字生成对应的图片)

目录 实现给出图集名字和图片名字生成对应的图片&#xff1a; 代码示例&#xff1a; dic: 键 是图集名称 值是一个字典 该字典键是图片名称 值是图片&#xff0c;结构如图&#xff1a; 测试代码&#xff1a; 结果&#xff1a; SpriteRenderer 讲解&#xff1a; Resour…

工业异常检测-CVPR2024-新的3D异常数据合成办法和自监督网络IMRNet

论文&#xff1a;https://arxiv.org/pdf/2311.14897v3.pdf 项目&#xff1a;https://github.com/chopper-233/anomaly-shapenet 这篇论文主要关注的是3D异常检测和定位&#xff0c;这是一个在工业质量检查中至关重要的任务。作者们提出了一种新的方法来合成3D异常数据&#x…

WPF编写工业相机镜头选型程序

该程序满足面阵和线阵的要求。 前端代码 <Window x:Class"相机镜头选型.MainWindow" Loaded"Window_Loaded"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…

springai结合ollama

目录 ollama 介绍 使用 下载&#xff1a; 安装&#xff1a; 点击这个玩意next就行了。 运行 spring ai使用ollama调用本地部署的大模型 加依赖 配置yml 写代码 ollama 介绍 官网&#xff1a;Ollama Ollama是一个用于部署和运行各种开源大模型的工具&#xff1b; …

Linux 统信UOS 设置程序“桌面快捷方式”与“开机自启动”

最近在统信uos系统 arm64架构上进行QT程序的开发&#xff0c;基本开发完毕后&#xff0c;开始着手准备程序的开机自启动模块&#xff0c;因为一般来说&#xff0c;程序在客户现场使用都是需要开机自启的。 然后在百度海淘&#xff0c;很少有这类相关的博客介绍&#xff0c;有一…

WiFi受限不再愁,电脑无网络快速修复指南

有时在试图连接WiFi时&#xff0c;会发现网络连接受限&#xff0c;或无法正常访问互联网。这种情况不仅影响了工作效率&#xff0c;还可能错过重要的信息。那么&#xff0c;究竟是什么原因导致了电脑WiFi连接受限呢&#xff1f;又该如何解决这一问题呢&#xff1f;小A今天就来教…