Linux自定义shell编写
Linux自定义shell编写
- 一.最终版本展示
- 1.动图展示
- 2.代码展示
- 二.具体步骤
- 1.打印提示符
- 2.解析命令行
- 3.分析是否是内建命令
- 1.shell对于内建名令的处理
- 2.cd命令
- 3.cd函数的实现
- 4.echo命令的实现
- 5.export命令的实现
- 6.内建命令函数的实现
- 4.创建子进程通过程序替换执行命令
- 5.循环往复即可
- 三.shell运行原理
经过了创建进程,终止进程,进程等待和进程程序替换之后,
我们就可以借助这些知识实现一个简单的shell命令行解释器了
温馨提示:
建议大家自己写一遍,这些代码分块之后每一个函数都很简单,
不过实现过程中可能会有各种各样非常细枝末节的地方被我们所忽视
因此可能会发生一看就懂,一写就废的情况…
一.最终版本展示
输入命令行时想要删除字符时不能直接按backspace,而是要按ctrl+backspace才能成功删除
1.动图展示
2.代码展示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//#define DEBUG 1
#define SEP " "char cwd[1024]={'\0'};
int lastcode=0;//上一次进程退出时的退出码char env[1024][1024]={'\0'};
int my_index=0;const char* getUsername()
{const char* username=getenv("USER");if(username==NULL) return "none";return username;
}const char* getHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname==NULL) return "none";return hostname;
}const char* getPwd()
{const char* pwd=getenv("PWD");if(pwd==NULL) return "none";return pwd;
}//分割字符串填入usercommand数组当中
//例如: "ls -a -l" 分割为"ls" "-a" "-l"
void CommandSplit(char* usercommand[],char* command)
{int i=0;usercommand[i++]=strtok(command,SEP);while(usercommand[i++]=strtok(NULL,SEP));
}//解析命令行
void GetCommand(char* command,char* usercommand[])
{command[strlen(command)-1]='\0';//清理掉最后的'\0'CommandSplit(usercommand,command);
#ifdef DEBUGint i=0;while(usercommand[i]!=NULL){printf("%d : %s\n",i,usercommand[i]);i++;}
#endif
}//创建子进程,完成任务
void Execute(char* usercommand[])
{pid_t id=fork();if(id==0){//子进程执行部分execvp(usercommand[0],usercommand);//如果子进程程序替换失败,已退出码为1的状态返回exit(1);}else{//父进程执行部分int status=0;//阻塞等待pid_t rid=waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}
}void cd(char* usercommand[])
{chdir(usercommand[1]);char tmp[1024]={'\0'};getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);lastcode=0;
} int echo(char* usercommand[])
{//1.echo后面什么都没有,相当于'\n'if(usercommand[1]==NULL){printf("\n");lastcode=0;return 1;}//2.echo $? echo $PWD echo $char* cmd=usercommand[1];int len=strlen(cmd);if(cmd[0]=='$' && len>1){//echo $?if(cmd[1]=='?'){printf("%d\n",lastcode);lastcode=0;}//echo $PWDelse{char* tmp=cmd+1;const char* env=getenv(tmp);//找不到该环境变量,打印'\n',退出码依旧为0if(env==NULL){printf("\n");}else{printf("%s\n",env);}lastcode=0;}}else{printf("%s\n",cmd);}return 1;
}void export(char* usercommand[])
{//exportif(usercommand[1]==NULL){lastcode=0;return;}strcpy(env[my_index],usercommand[1]);putenv(env[my_index]);my_index++;
}int doBuildIn(char* usercommand[])
{//cdif(strcmp(usercommand[0],"cd")==0){if(usercommand[1]==NULL) return -1;cd(usercommand);return 1;}//echoelse if(strcmp(usercommand[0],"echo")==0){return echo(usercommand);}//exportelse if(strcmp(usercommand[0],"export")==0){export(usercommand);}return 0;
}int main()
{while(1){//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);char* usercommand[1024]={NULL};//2.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//4.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}
二.具体步骤
1.打印提示符
const char* getUsername()
{const char* username=getenv("USER");if(username==NULL) return "none";return username;
}const char* getHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname==NULL) return "none";return hostname;
}const char* getPwd()
{const char* pwd=getenv("PWD");if(pwd==NULL) return "none";return pwd;
}int main()
{while(1){//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);}return 0;
}
注意:
1.因为scanf默认读取到空格或者’\n’时就会停止继续读取,
可是我们命令行中要求读取到空格也不能停止,(否则我们的指令就无法带选项了,因为选项之间是用空格来分割的)
因此我们需要fgets函数
char command[1024]={'\0'};
fgets(command,sizeof(command),stdin);
从命令行当中读取一行字符串
2.因为我们用户输入的时候在最后的时候一定会输入一个’\n’,因此我们需要把’\n’置为’\0’
command[strlen(command)-1]='\0';//去除我们最后输入的'\n'
把’\n’置为’\0’的操作我们放到了下一个函数当中来完成
2.解析命令行
因为有些命令带有选项:例如 “ls -a -l”
我们在进行程序替换的时候需要分别传入"ls" “-a” "-l"这几个字符串,所以需要把用户输入的字符串分割为若干个字符串存放到一个指针数组当中,可以使用strtok字符串切割函数
#define DEBUG 1
#define SEP " "//分割字符串填入usercommand数组当中
//例如: "ls -a -l" 分割为"ls" "-a" "-l"
void CommandSplit(char* usercommand[],char* command)
{int i=0;usercommand[i++]=strtok(command,SEP);while(usercommand[i++]=strtok(NULL,SEP));
}//解析命令行
void GetCommand(char* command,char* usercommand[])
{command[strlen(command)-1]='\0';//清理掉最后的'\0'CommandSplit(usercommand,command);
#ifdef DEBUGint i=0;while(usercommand[i]!=NULL){printf("%d : %s\n",i,usercommand[i]);i++;}
#endif
}
我们可以使用条件编译来方便我们自由选择是否需要打印
解析命令行后的usercommand数组中的内容
3.分析是否是内建命令
1.shell对于内建名令的处理
下面我们就一起来实现一下
cd,echo,export这几个内建命令
2.cd命令
可是如果shell进行进程程序替换了,那么shell执行完之后不就没了吗?
因此shell执行内建命令时直接封装函数调用系统调用接口即可
内建命令的个数是有限的,所以shell是可以对内建命令进行穷举的
因此我们就能更好地理解内建命令了:
内建命令:不需要创建子进程执行,shell自己执行,本质就是调用系统调用接口
3.cd函数的实现
对于cd而言,我们可以调用chdir这个系统调用接口来改变当前进程的工作目录
同时也可以设置一个全局的char类型的数组cwd来保存当前路径
每次cd之后用cwd来记录新路径,然后通过putenv来修改环境变量PWD
让我们第一步打印的提示符中的PWD路径可以动态调整
//cwd:存放当前路径,是一个全局变量
char cwd[1024]={'\0'};void cd(char* usercommand[])
{//1.chdir改变当前进程的工作目录chdir(usercommand[1]);//2.获取当前进程所在工作目录到tmp数组当中char tmp[1024]={'\0'};getcwd(tmp,sizeof(tmp));//3.把tmp数组中的内容格式化为"PWD=tmp数组中的内容"放到cwd数组当中sprintf(cwd,"PWD=%s",tmp);//4.导入环境变量putenv(cwd);//5.最后一次进程的退出码置为0(是为了echo $?获取最后一次进程的退出码的实现,跟cd无关)lastcode=0;
}
注意:cwd数组必须是全局变量,如果cwd是局部变量,那么出了cd这个函数之后cwd数组就会销毁,其中的内容也将会消失
而我们putenv导入环境变量时其实是把cwd的地址放入了环境变量当中,
而不是拷贝一份这个cwd放入环境变量当中,因此cwd数组销毁时,对应导入的环境变量中的内容就不是原来的内容了
4.echo命令的实现
echo命令也是一个内建命令
echo 空串 打印换行
echo $ ? 打印上次进程退出时的退出码
echo $环境变量 打印环境变量
echo 字符串 打印字符串
注意:
echo $ 打印$这个字符串
因此即使判断了$,还要继续判断$后面还有没有字符
//全局变量
int lastcode=0;//上一次进程退出时的退出码int echo(char* usercommand[])
{//1.echo后面什么都没有,相当于'\n'if(usercommand[1]==NULL){printf("\n");lastcode=0;return 1;}//2.echo $? echo $PWD echo $char* cmd=usercommand[1];int len=strlen(cmd);if(cmd[0]=='$' && len>1){//echo $?if(cmd[1]=='?'){printf("%d\n",lastcode);lastcode=0;}//echo $PWDelse{char* tmp=cmd+1;const char* env=getenv(tmp);//找不到该环境变量,打印'\n',退出码依旧为0if(env==NULL){printf("\n");}else{printf("%s\n",env);}lastcode=0;}}else{printf("%s\n",cmd);}return 1;
}
5.export命令的实现
export导入环境变量
注意:
1.刚才介绍cd函数的时候.我们说明了环境变量导入时其实是导入的对应字符串的地址
因此我们环境变量字符串必须要保证全局有效
2.由于我们可以导入很多环境变量,因此env需要是一个二维数组,同时还需要一个index下标来标记该数组当中已经导入过的环境变量
char env[1024][1024]={'\0'};
int my_index=0;void export(char* usercommand[])
{//1.export后面什么都没跟,什么都不执行,直接返回即可if(usercommand[1]==NULL){lastcode=0;return;}//2.要导入的环境变量拷贝到env数组当中strcpy(env[my_index],usercommand[1]);//3.将env数组当中的环境变量导入该进程当中putenv(env[my_index]);my_index++;
}
6.内建命令函数的实现
写好了cd,echo,export这几个函数之后,我们只需要在内建命令函数当中调用这几个函数即可
//返回值=0,说明不是内建命令
//返回值=1,说明是内建命令并且执行成功
int doBuildIn(char* usercommand[])
{//cdif(strcmp(usercommand[0],"cd")==0){if(usercommand[1]==NULL) return -1;cd(usercommand);return 1;}//echoelse if(strcmp(usercommand[0],"echo")==0){return echo(usercommand);}//exportelse if(strcmp(usercommand[0],"export")==0){export(usercommand);}return 0;
}int main()
{while(1){//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);char* usercommand[1024]={NULL};//2.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//4.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}
4.创建子进程通过程序替换执行命令
使用execvp这个函数来进行程序替换
带上v:因为我们的命令都是放在数组当中的
带上p:因为我们输入的都是系统指令,带上p才可以自动在环境变量中查找我们的命令
否则就要显式传入路径
注意lastcode的设置
//创建子进程,完成任务
void Execute(char* usercommand[])
{pid_t id=fork();if(id==0){//子进程执行部分execvp(usercommand[0],usercommand);//如果子进程程序替换失败,已退出码为1的状态返回exit(1);}else{//父进程执行部分int status=0;//阻塞等待pid_t rid=waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}
}
5.循环往复即可
main函数加上while(1)死循环即可
int main()
{while(1){//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);char* usercommand[1024]={NULL};//2.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//4.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}
三.shell运行原理
shell内部提取用户输入的命令行进行解析
判断是否是内建命令,
1.如果是内建命令的话,shell自己通过调用自己封装的函数来执行该命令
2.如果不是内建命令,shell创建子进程,通过程序替换来然子进程执行该命令
shell进程阻塞等待回收子进程的退出状态
然后循环往复
以上就是Linux自定义shell编写的全部内容,希望能对大家有所帮助!
相关文章:

Linux自定义shell编写
Linux自定义shell编写 一.最终版本展示1.动图展示2.代码展示 二.具体步骤1.打印提示符2.解析命令行3.分析是否是内建命令1.shell对于内建名令的处理2.cd命令3.cd函数的实现4.echo命令的实现5.export命令的实现6.内建命令函数的实现 4.创建子进程通过程序替换执行命令5.循环往复…...

堆的应用:堆排序和TOP-K问题
上次才讲完堆的相关问题:二叉树顺序结构与堆的概念及性质(c语言实现堆 那今天就接着来进行堆的主要两方面的应用:堆排序和TOP-K问题 文章目录 1.堆排序1.1概念、思路及代码1.2改良代码(最初建立大堆用AdjustDow) 2. TO…...

element表格排序功能
官方展示 个人项目 可以分别对每一项数据进行筛选 注:筛选的数据不能是字符串类型必须是数字类型,否则筛选会乱排序 html <el-table :data"tableData" border height"600" style"width: 100%"><el-table-co…...
HNU-Java程序设计基础训练-2023
1.DNA序列(Java) 【问题描述】 一个DNA序列由A/C/G/T四个字母的排列组合组成。G和C的比例(定义为GC-Ratio)是序列中G和C两个字母的总的出现次数除以总的字母数目(也就是序列长度)。在基因工程中…...
数据库和数据库编程
数据库、数据表、表数据操作以及数据库编程相关的知识点 1. 数据库的概念: 数据库是用于存储和组织数据的系统。数据库管理系统(DBMS)是管理数据库的软件,提供对数据的访问、查询和维护。关系型数据库是一种通过表格结构来组织和管理数据的数据库。 2…...

爬虫基础一(持续更新)
爬虫概念: 通过编写程序,模拟浏览器上网,然后让其去互联网上抓取数据的过程 分类: 1,通用爬虫:抓取一整张页面数据 2,聚焦爬虫:抓取页面中的局部内容 3,增量式爬虫&…...

右键菜单“以notepad++打开”,在windows文件管理器中
notepad 添加到文件管理器的右键菜单中 找到安装包,重新安装一般即可。 这里有最新版:地址 密码:f0f1 方法 在安装的时候勾选 “Context Menu Entry” 即可 Notepad的右击打开文件功能 默认已勾选 其作用是添加右键快捷键。即,对于任何…...
JSON.parseObject强制将自动转化的Intage型设置为Long型
通过Redis或Caffeine存储入json型String,通过JSON.parseObject自动类型转化之后,数值会优先转为Intage,如果存入的字符值大于Intage最大值,会自动转为Long型; 需求是:实要取出时数值类型值为Long࿱…...

Redis的集群模式:主从 哨兵 分片集群
基于Redis集群解决单机Redis存在的问题,在之前学Redis一直都是单节点部署 单机或单节点Redis存在的四大问题: 数据丢失问题:Redis是内存存储,服务重启可能会丢失数据 > 利用Redis数据持久化的功能将数据写入磁盘并发能力问题…...

Note: An Interesting Festival
An Interesting Festival 一个有趣的节日。 festival The Agricultural Feast takes place after the independence Day. 农业盛会在独立日后举行 takes place independence feast agricultural It is not a worldwide celebration. 它不是一个全球的庆典。 worldwide ce…...

iview表格固定列横向滚动条无法拖动问题
文章目录 问题解决办法 问题 在使用iview的表格组件时,遇到了设置固定列表格后滚动条无法拖动的问题,当对表格列进行固定后,底部的横向滚动条就无法拖动了,主要的问题就是固定区域盖住了横向滚动条。 解决办法 在组件内直接加下…...

Python序列之集合
系列文章目录 Python序列之列表Python序列之元组Python序列之字典Python序列之集合(本篇文章) Python序列之集合 系列文章目录前言一、集合是什么?二、集合的操作1.集合的创建(1)使用{}创建(2)…...

智慧园区物联综合管理平台之架构简述
总体架构 系统总体划分为物联感知系统层、 核心平台层、 综合运营服务平台和展示层四部分。 物联感知系统层 物联感知系统主要是支撑园区智能化运行的各子系统, 包括门禁系统、 视频监控系统、 车辆管理系统等。 核心平台层 核心平台层包括: 园区物联综合管理平台和园区…...

国科大图像处理2023速通期末——汇总2017-2019
国科大2023.12.28图像处理0854期末重点 图像处理 王伟强 作业 课件 资料 一、填空 一个阴极射线管它的输入与输出满足 s r 2 sr^{2} sr2,这将使得显示系统产生比希望的效果更暗的图像,此时伽马校正通常在信号进入显示器前被进行预处理,令p…...

oracle 9i10g编程艺术-读书笔记2
配置Statspack 安装Statspack需要用internal身份登陆,或者拥有SYSDBA(connect / as sysdba)权限的用户登陆。需要在本地安装或者通过telnet登陆到服务器。 select instance_name,host_name,version,startup_time from v$instance;检查数据文件路径及磁盘空间&…...

PACC:数据中心网络的主动 CNP 生成方案
PACC:数据中心网络的主动 CNP 生成方案 文章目录 PACC:数据中心网络的主动 CNP 生成方案PACC算法CNP数据结构PACC参数仿真结果参考文献 PACC算法 CNP数据结构 PACC参数 仿真结果 PACC Hadoop Load0.2 的情况: PACC Hadoop Load0.4 的情况&a…...

我最喜欢的趣味几何书-读书笔记
我最喜欢的趣味几何书-读书笔记 1、利用阴影的长度来测量 公元前6世纪,古希腊哲学家泰勒思为了测量金字塔,想到了这样的方法:选择了一个特殊的时间,在那个时间,他自身的影子长度刚好跟他的身高相等。此时,…...

Stable Diffusion模型概述
Stable Diffusion 1. Stable Diffusion能做什么?2. 扩散模型2.1 正向扩散2.2 反向扩散 3. 训练如何进行3.1 反向扩散3.2 Stable Diffusion模型3.3 潜在扩散模型3.4 变分自动编码器3.5 图像分辨率3.6 图像放大 4. 为什么潜在空间是可能的?4.1 在潜在空间中…...

二叉树详解(深度优先遍历、前序,中序,后序、广度优先遍历、二叉树所有节点的个数、叶节点的个数)
目录 一、树概念及结构(了解) 1.1树的概念 1.2树的表示 二、二叉树概念及结构 2.1概念 2.2现实中的二叉树: 2.3数据结构中的二叉树: 2.4特殊的二叉树: 2.5 二叉树的存储结构 2.51 顺序存储: 2.5.2 链式存储&…...

C++日期类的实现
前言:在类和对象比较熟悉的情况下,我们我们就可以开始制作日期表了,实现日期类所包含的知识点有构造函数,析构函数,函数重载,拷贝构造函数,运算符重载,const成员函数 1.日期类的加减…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...

力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
文章目录 一、开启慢查询日志,定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...

解析“道作为序位生成器”的核心原理
解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制,重点解析"道作为序位生成器"的核心原理与实现框架: 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...