【Linux】实现一个简易的shell命令行

news2024/11/27 14:41:05

🦄个人主页:修修修也

🎏所属专栏:Linux

⚙️操作环境:Xshell (操作系统:Ubuntu 22.04 server 64bit)


目录

一.项目简介

二.分析项目实现

三.逐步实现项目功能

1.获取命令行

2.解析命令行

3.指令的判断

4.普通命令的执行

四.完整项目代码

结语


一.项目简介

        在使用Linux的过程中,相信大家最熟悉的就是Linux的命令行使用方式了,我们可以给命令行输入任意有效指令, 然后命令行会根据我们输入的指令来完成相应的操作。

        今天我们尝试在Linux使用C语言自己实现一个简单的shell命令行程序,它可以像真的命令行那样执行命令, 与程序员交互, 话不多说, 先来看看实现效果吧:

myshell功能测试


二.分析项目实现

        用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

        然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)

三.逐步实现项目功能

        该部分只讲功能实现的代码逻辑, 故可能不会包含宏定义和全局变量等实现细节,如需完整的项目代码,请移步本文第四部分.

1.获取命令行

        我们将获取命令行做成一个循环,除非用户主动退出,否则一直保持命令行接收指令的状态:

int main()
{	
	while(!quit)
	{
		//2.交互问题,获取命令行内容
		interact(commandline,sizeof(commandline));

		//3.分割命令字符串strtok(),解析命令行
		int argc = splitstring(commandline, argv);
		if(argc == 0) continue;

        //4.指令的判断
		int n = buildCommand(argv,argc);

		//5.普通命令的执行
		if(!n) NormalExcute(argv);
	}
	return 0;
}

        具体的获取命令行逻辑如下函数:

const char* getusername()
{
	//通过getenv()获取环境变量中的用户名
	return getenv("USER");
}

void getpwd()
{
	//通过getcwd系统接口获取并更新pwd
	getcwd(pwd,sizeof(pwd));
}

void interact(char *cline, int size)
{
	//需要环境变量相关的系统调用函数来获取命令行提示信息

    //获取主机名
	char hostname[64];
	gethostname(hostname,sizeof(hostname));
    
    //1.打印bash命令行前面的提示信息   
	getpwd();
	printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),hostname,pwd);
	
	//2.接收用户输入信息
	fgets(cline, size,stdin);
	assert(cline != NULL);
	(void)cline;	//防止编译器报错定义而未使用的变量(假装用一下)
	
	cline[strlen(cline)-1] = '\0';
}

2.解析命令行

        解析命令行主要就是将获取到的字符串按空格切分开来放入一个新数组中,我们使用strtok()来完成这个工作, 具体实现代码如下:

int splitstring(char cline[], char *_argv[])
{
	int i = 0;
	_argv[i++] = strtok(cline, DELIM);
	while(_argv[i++] = strtok(NULL, DELIM));

	return i-1;
}

3.指令的判断

        我们虽然可以借助fork()创建子进程来代替我们实现诸多普通命令, 但是对于很多内建命令来说, 创建子进程执行命令的结果并不会影响父进程, 这会导致父进程命令无效, 因此对于内建命令我们要先判断,再让父进程自主完成这些内建命令, 代码如下:

int buildCommand(char *_argv[],int _argc)
{	
		//4.指令的判断

        //cd命令
		if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
		{
			//更改目录
			chdir(_argv[1]);
			getpwd();
			//更改环境变量
			sprintf(getenv("PWD"),"%s",pwd);
			return 1;
		}

        //export命令
		else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
		{
			//因为_argv一直被我们用来存新的指令,环境变量会因此被覆盖
			//所以需要一个固定的存环境变量的地方来保存环境变量
			strcpy(myenv,_argv[1]);
			putenv(myenv);
			return 1;
		}
        
        //echo命令
		else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
		{
			if(strcmp(_argv[1],"$?") == 0)
			{
				printf("%d\n",lastcode);
				lastcode = 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;
		}

        //ls命令
		if(strcmp(_argv[0],"ls") == 0)
		{
			_argv[_argc++] = "--color";
			_argv[_argc] = NULL;
		}

		return 0;
}

4.普通命令的执行

        普通命令的执行不会影响父进程,因此我们可以使用fork()创建子进程,然后使用exec*系列进程替换函数来完成相关操作, 代码如下:

void NormalExcute(char *_argv[])
{
	//5.普通命令的执行
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork error");
		return;
	}
	else if(id == 0)
	{
		//让子进程执行普通命令
		//execvpe(_argv[0],_argv,environ);
		execvp(_argv[0],_argv);
		exit(EXIT_CODE);
	}
	else
	{
        //父进程等待子进程返回值
		int status = 0;
		pid_t rid = waitpid(id,&status,0);
		if(rid == id)
		{
			lastcode = WEXITSTATUS(status);
		}
	}
}

四.完整项目代码

        完整项目代码如下:

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

#define LEFT "["
#define RIGHT "]"
#define LABLE "$"
#define DELIM " \t"

#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 55


int lastcode = 0;
int quit = 0;

char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];

//自定义环境变量表,做成二维数组就需要维护了
char myenv[LINE_SIZE];
//自定义本地变量表

const char* getusername()
{
	//通过getenv()获取环境变量中的用户名
	return getenv("USER");
}

void getpwd()
{
	//通过getenv()获取环境变量中的路径
	//return getenv("PWD");
	getcwd(pwd,sizeof(pwd));
}

void interact(char *cline, int size)
{
	//1.打印bash命令行前面的提示信息
	//需要环境变量相关的系统调用函数来获取命令行提示信息
	char hostname[64];
	gethostname(hostname,sizeof(hostname));
	getpwd();
	printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),hostname,pwd);
	
	//2.接收用户输入信息
	fgets(cline, size,stdin);
	assert(cline != NULL);
	(void)cline;	//防止编译器报错定义而未使用的变量(假装用一下)
	
	cline[strlen(cline)-1] = '\0';
}

int splitstring(char cline[], char *_argv[])
{
	int i = 0;
	_argv[i++] = strtok(cline, DELIM);
	while(_argv[i++] = strtok(NULL, DELIM));

	return i-1;

}

void NormalExcute(char *_argv[])
{
	//5.普通命令的执行
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork error");
		return;
	}
	else if(id == 0)
	{
		//让子进程执行普通命令
		//execvpe(_argv[0],_argv,environ);
		execvp(_argv[0],_argv);
		exit(EXIT_CODE);
	}
	else
	{
		int status = 0;
		pid_t rid = waitpid(id,&status,0);
		if(rid == id)
		{
			lastcode = WEXITSTATUS(status);
		}
	}
}

int buildCommand(char *_argv[],int _argc)
{	
		//4.指令的判断
		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)
		{
			//因为_argv一直被我们用来存新的指令,环境变量会因此被覆盖
			//所以需要一个固定的存环境变量的地方来保存环境变量
			strcpy(myenv,_argv[1]);
			putenv(myenv);
			return 1;
		}

		else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
		{
			if(strcmp(_argv[1],"$?") == 0)
			{
				printf("%d\n",lastcode);
				lastcode = 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;
}

int main()
{	
	while(!quit)
	{
		//2.交互问题,获取命令行内容
		interact(commandline,sizeof(commandline));

		//printf("echo:%s\n",commandline);

		//3.分割命令字符串strtok(),解析命令行
		int argc = splitstring(commandline, argv);
		if(argc == 0) continue;

        //4.指令的判断
		int n = buildCommand(argv,argc);

		//5.普通命令的执行
		if(!n) NormalExcute(argv);
	}
	return 0;
}

结语

希望这篇关于 在Linux中实现一个简易的shell命令行 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!

相关文章推荐

【Linux】操作系统与进程

【Linux】实现三个迷你小程序(倒计时,旋转指针,进度条)

【Linux】手把手教你从零上手gcc/g++编译器

【Linux】手把手教你从零上手Vim编辑器

【Linux】一文带你彻底搞懂权限

【Linux】基本指令(下)

【Linux】基本指令(中)

【Linux】基本指令(上)


        今天是2024.10.24, 祝广大程序员们:

                "编"出未来,"程"就梦想!

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

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

相关文章

计算生物学与生物信息学漫谈-1-测序一路走来

最近工作中&#xff0c;反思自己计算生物学基础非常薄弱&#xff0c;然而作为一门非常新兴的交叉学科&#xff0c;涉及计算机、物理、生物、数学等多多学科&#xff0c;国内并没有这样完善的教程&#xff0c;因此想要自己做一个教程&#xff0c;使用费曼学习法学习&#xff0c;…

【亚马逊云】基于 Amazon EKS 搭建开源向量数据库 Milvus

文章目录 一、先决条件1.1 安装AWS CLI ✅1.2 安装 EKS 相关工具✅1.3 创建 Amazon S3 存储桶✅1.4 创建 Amazon MSK 实例✅ 二、创建EKS集群三、创建 ebs-sc StorageClass四、安装 AWS Load Balancer Controller五、部署 Milvus 数据库5.1 添加 Milvus Helm 仓库5.2 配置 S3 作…

Vue2、Element中实现Enter模拟Tab,实现切换下一个框的效果

目录 &#x1f4c3;前序 &#x1f449;开发历程 &#x1f4bb;实际代码 &#x1f4fd;实现效果图 前序 在几乎所有的浏览器中&#xff0c;都具备通过 Tab 键来切换焦点的功能。然而&#xff0c;有些用户提出了强烈要求&#xff0c;希望能够增加通过 Enter 键…

进程间通信(二)消息队列、共享内存、信号量

文章目录 进程间通信System V IPC概述System V IPC 对象的访问消息队列示例--使用消息队列实现进程间的通信 共享内存示例--使用共享内存实现父子进程间的通信&#xff08;进程同步&#xff09;示例--使用进程实现之前的ATM案例&#xff08;进程互斥&#xff09; 信号量示例--利…

上传Gitee仓库流程图

推荐一个流程图工具 登录 | ProcessOnProcessOn是一个在线协作绘图平台&#xff0c;为用户提供强大、易用的作图工具&#xff01;支持在线创作流程图、思维导图、组织结构图、网络拓扑图、BPMN、UML图、UI界面原型设计、iOS界面原型设计等。同时依托于互联网实现了人与人之间的…

大厂常问iOS面试题–Runloop篇

大厂常问iOS面试题–Runloop篇 一.RunLoop概念 RunLoop顾名思义就是可以一直循环(loop)运行(run)的机制。这种机制通常称为“消息循环机制” NSRunLoop和CFRunLoopRef就是实现“消息循环机制”的对象。其实NSRunLoop本质是由CFRunLoopRef封装的&#xff0c;提供了面向对象的AP…

6个RAG进阶优化方案,对应14篇论文案例解析

本文对近几月我了解到的RAG优化策略进行总结, 每个优化策略都有相应的研究论文作为支撑。在01先总结优化方向, 02细化说明相应论文 在介绍RAG优化策略之前, 先说说知识库数据处理: 增强数据粒度&#xff1a;旨在提升文本标准化、一致性、事实准确性和丰富的上下文&#xff0c…

Acrel-1000变电站综合自动化系统及微机在化工企业中的应用方案

文&#xff1a;安科瑞郑桐 摘要&#xff1a;大型化工企业供配电具有的集约型特点&#xff0c;化工企业内35kV变电站和10kV变电所数量大、分布广&#xff0c;对于老的大多大型及中型化工企业而言&#xff0c;其变电站或变电所内高压电气设备为旧式继电保护装置&#xff0c;可靠…

详解Java的类文件结构(.class文件的结构)

this_class 指向常量池中索引为 2 的 CONSTANT_Class_info。super_class 指向常量池中索引为 3 的 CONSTANT_Class_info。由于没有接口&#xff0c;所以 interfaces 的信息为空。 对应 class 文件中的位置如下图所示。 06、字段表 一个类中定义的字段会被存储在字段表&#x…

zotero文献管理学习

1 zotero软件简介 zotero是一款开源的文献管理软件。如果你听说或使用过EndNote&#xff0c;那么可能会对“文献管理”有一定的概念。可以简单地这样理解&#xff1a;zotero一定程度上可以作为EndNote的平替。 EndNote需要注册付费&#xff0c;对于无专业科研机构隶属关系的企…

MATLAB运动目标检测系统

应用背景 运动目标的定位跟踪&#xff0c;检测识别&#xff0c;运动分析在图像压缩、运动分析、交通检测&#xff0c;智能监控等方面有主要的应用。 首先&#xff0c;在图像压缩中&#xff0c;运动目标检测技术可以在背景区域中将前景区域提取分割出来&#xff0c;只传递部分…

植物端粒到端粒(T2T)基因组研究进展与展望

鼠鼠跳槽了&#xff0c;因为现在公司发(bu)展(zhang)受(gong)限(zi)&#xff0c;只能跳一次&#xff0c;从大兴到昌平了。从二代ivd行业去三代T2T和泛基因组了。在这里我们分享一篇文章。 摘要&#xff1a;高质量的参考基因组是基因组学研究的基础。目前&#xff0c;大多数的参…

笨蛋学习FreeMarker

笨蛋学习FreeMarker FreeMarker参考网址创建实例引入Maven创建工具类创建实例并进行输出 FreeMarker数据类型布尔型&#xff1a;日期型&#xff1a;数值型&#xff1a;字符型&#xff1a;需要处理字符串为null的情况&#xff0c;否则会报错字符串为空不会报错cap_firstuncap_fi…

【银河麒麟高级服务器操作系统实例】金融行业TCP连接数猛增场景的系统优化

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 服务器环境以及配置 物理机/虚拟机/云/容器 物理…

12 django管理系统 - 注册与登录 - 登录

为了演示方便&#xff0c;我就直接使用models里的Admin来演示&#xff0c;不再创建用户模型了。 ok&#xff0c;先做基础配置 首先是在base.html中&#xff0c;新增登录和注册的入口 <ul class"nav navbar-nav navbar-right"><li><a href"/ac…

使用 VSCode 通过 Remote-SSH 连接远程服务器详细教程

使用 VSCode 通过 Remote-SSH 连接远程服务器详细教程 在日常开发中&#xff0c;许多开发者需要远程连接服务器进行代码编辑和调试。Visual Studio Code&#xff08;VSCode&#xff09;提供了一个非常强大的扩展——Remote-SSH&#xff0c;它允许我们通过 SSH 协议直接连接远程…

一图读懂“低空经济”

&#x1f482; 个人主页: 同学来啦&#x1f91f; 版权: 本文由【同学来啦】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助&#xff0c;欢迎关注、点赞、收藏和订阅专栏哦 文章目录 ✈️ 一、低空经济简介&#x1f534; 1、基本含义&#x1f7e0; 2、…

【免费领取】基于javaweb实现的的日志管理系统

主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 项目描述 本工作日志管理系统是一个面向中小企业的简单的工作管理系统&#xff0c;它主要实现公…

【Python】Pandas基础操作手册(上)

哈喽&#xff0c;哈喽&#xff0c;大家好~ 我是你们的老朋友&#xff1a;保护小周ღ 今天给大家带来的是【Python】Pandas基础操作手册&#xff08;上&#xff09;本次主要讲解, python pandas 模块的一些基本概念, 以及了解 Dataframe 对象的创建, 赋值, 保存. 一起来看看叭…

【SpringBoot】17 多文件上传(Thymeleaf + MySQL)

Git仓库 https://gitee.com/Lin_DH/system 文件上传 可参考上一篇【SpringBoot】16 文件上传&#xff08;Thymeleaf MySQL&#xff09; https://blog.csdn.net/weixin_44088274/article/details/143004298 介绍 文件上传是指将本地的图片、视频、音频等文件上传到服务器&…