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.日期类的加减…...
从清洗到展示:一份完整的微博评论LDA分析Jupyter Notebook实战笔记(附避坑点)
从清洗到展示:一份完整的微博评论LDA分析Jupyter Notebook实战笔记(附避坑点) 在数据爆炸的时代,社交媒体评论中蕴藏着大量有价值的用户观点。本文将带你用Jupyter Notebook完整走通微博评论的主题分析流程,从原始数据…...
【网络】Wireshark实战:TCP连接异常之RST报文深度解析
1. 认识TCP的RST报文:网络世界的紧急刹车 第一次在Wireshark里看到RST标志位时,我正盯着满屏的TCP握手包发呆。那个鲜红的[RST, ACK]就像交通信号灯突然变红,让原本流畅的数据传输戛然而止。简单来说,RST(Reset&#x…...
Stable Diffusion Lora训练避坑指南:为什么你的模型总是‘丑’?
Stable Diffusion LoRA训练实战:从“翻车”到精通的避坑手册 每次看到别人分享的精致LoRA模型,再看看自己训练出的"克苏鲁风格"作品,是不是有种砸键盘的冲动?别急着放弃——这可能是你训练流程中几个关键环节出了问题。…...
别再手动调参了!用Matlab+NRBO-BP+NSGAII搞定工艺优化,自动生成最优参数组合Excel
工艺优化新范式:基于NRBO-BP与NSGAII的智能参数寻优系统 在化工、材料、制造等领域的工艺优化过程中,工程师和研究人员常常面临一个共同的挑战:如何在有限的实验资源和时间约束下,从海量的参数组合中找到最优解。传统的手动调参方…...
FreeRTOS实战:基于串口空闲中断与二值信号量构建高效数据接收框架
1. 串口通信的痛点与解决方案 在嵌入式开发中,串口通信是最基础也最常用的外设之一。但处理不定长数据时,很多开发者会遇到这样的困扰:要么频繁进入接收中断导致CPU负载过高,要么需要手动设置数据包长度增加协议复杂度。我在早期项…...
python协同过滤就业 大学生就业求职网vue 可视化统计echart
目录python协同过滤在就业领域的应用大学生就业求职网的功能需求VueECharts实现可视化统计1. **多维数据看板**2. **实时交互分析**3. **移动端适配**技术栈整合建议项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作python协同过…...
深入理解Fritzing电路仿真:5个专业级电子设计验证技巧
深入理解Fritzing电路仿真:5个专业级电子设计验证技巧 【免费下载链接】fritzing-app Fritzing desktop application 项目地址: https://gitcode.com/gh_mirrors/fr/fritzing-app Fritzing是一款开源的电子设计自动化(EDA)软件&#x…...
FlowState Lab 保姆级Docker容器化部署与运维实战
FlowState Lab 保姆级Docker容器化部署与运维实战 1. 前言:为什么选择Docker部署FlowState Lab 如果你正在寻找一种简单高效的方式来部署FlowState Lab模型,Docker容器化无疑是最佳选择。想象一下,你花了一周时间在本地调试好的模型&#x…...
掌握AI落地三件套:微调、Agent、部署,让你薪资直冲20K+!
文章核心内容是介绍AI行业高薪技能,即掌握大模型落地的“三件套”:微调、Agent、部署。微调是将通用模型变为专属专家的关键,Agent开发让模型能自动解决问题,部署则是基础但重要的能力。文章还强调了传统AI基础的重要性࿰…...
OS17.【Linux】进程基础知识(1)
目录 1.浅层定义 程序和进程的区别 2.查看进程的方法 ps ajx top 查看/proc目录 编辑 PID 3.手动用ps查看自己运行的程序 在/proc手动查看自己运行的程序目录 杀死进程的常用方法 进程目录中的文件 cwd 理解"当前路径"的含义 4.如何管理一个进程 程…...
