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编写的全部内容,希望能对大家有所帮助!

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

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

相关文章

【开源】基于Vue+SpringBoot的公司货物订单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…

多功能演示工具ProVideoPlayer2 mac特色介绍

ProVideoPlayer2 mac是用于大多数任何生产的首选多功能演示工具。ProVideoPlayer 2是一种动态视频播放和处理媒体服务器&#xff0c;可将视频映射&#xff08;包括播放和实时视频输入&#xff09;实时控制到一个或多个输出。包括实时效果&#xff0c;调度&#xff0c;网络同步和…

Spring-5-切入点的高级使用

Spring提供了两个额外的Pointcut实现&#xff0c;分别是ComposablePointcut和ControlFlowPointcut,它们提供了所需的灵活性。 使用控制流切入点 由ControlFlowPointcut类实现的Spring控制流切入点类似于许多其他AOP实现中可用的cflow构造&#xff0c;尽管功能上没有那么强大。…

Python+Yolov5+Qt交通标志特征识别窗体界面相片视频摄像头

程序示例精选 PythonYolov5Qt交通标志特征识别窗体界面相片视频摄像头 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonYolov5Qt交通标志特征识别窗体界面相片视频摄像头》编写代码&a…

电子合同如何验证有效性?教你一眼避雷无效合同!

你是否收到过电子合同&#xff0c;却对它是否有效感到困惑&#xff1f; 要验证电子合同是否有效&#xff0c;首先要看它的电子签章是否符合要求。 电子签名怎么才可靠&#xff1f; 电子签名同时满足以下条件&#xff0c;就是可靠签名&#xff0c;也就是具有法律效力的电子签名…

Unity 旋转跟随

Unity 使用任意一个局部轴指向目标 效果&#xff1a; 主要用于在编辑器中可视化对象的朝向&#xff0c;同时提供了选择不同轴向的功能。在运行时&#xff0c;物体将根据所选择的轴向朝向目标&#xff0c;并在 Scene 视图中绘制一个带箭头的圆环。 定义轴向枚举&#xff1a;…

车联网的安全风险与应对措施

安全风险 1、恶意软件 攻击者可以通过入侵厂商或供应商网络&#xff0c;用恶意软件&#xff08;如病毒、木马、勒索软件等&#xff09;感染车联网系统组件&#xff0c;从而获得对车辆的控制权或窃取敏感信息。例如&#xff0c;一名安全研究人员成功入侵了特斯拉&#xff08;T…

校园安全风险管理系统

随着社会的不断发展和进步&#xff0c;教育行业也逐渐走向了信息化、智能化管理。校园作为教育事业的重要场所&#xff0c;其安全和管理问题一直备受关注。为了确保校园的安全和管理&#xff0c;各种校园管理平台应运而生。 其中&#xff0c;凡尔码搭建的校园安全管理系统为校园…

CodeWhisperer:编码世界中的声音启迪者

人烟 导语&#xff1a; 在数字化时代&#xff0c;编码已经成为了一种不可或缺的技能。而 CodeWhisperer&#xff08;编码世界中的声音启迪者&#xff09;则以其卓越的技术和深厚的知识为人们带来了独特的启发和指导。本文将介绍 CodeWhisperer 的背景和成就&#xff0c;探讨他是…

PowerShell Instal 一键部署gitea

gitea 前言 Gitea 是一个轻量级的 DevOps 平台软件。从开发计划到产品成型的整个软件生命周期,他都能够高效而轻松的帮助团队和开发者。包括 Git 托管、代码审查、团队协作、软件包注册和 CI/CD。它与 GitHub、Bitbucket 和 GitLab 等比较类似。 Gitea 最初是从 Gogs 分支而来…

土壤侵蚀模型地形因子计算工具(2.0版本)

土壤侵蚀模型地形因子计算工具&#xff08;2.0版本&#xff09; 土壤侵蚀模型地形因子计算工具&#xff08;2.0版本)。该软件为符素华老师、刘宝元老师和周贵云老师开发的。2.0 版本在 1.0 版本的基础上修改了当坡长大于临界坡长后的坡长因子算法。2.0 中用整坡坡长因子计算公式…

nfs高可用部署

一、前言 为了避免nfs单点问题导致的服务不可用&#xff0c;使用以下架构实现nfs的高可用&#xff0c;keepalivedinotifyrsyncnfs&#xff0c;keepalived实现nfs的高可用&#xff0c;inotify持续监控nfs数据目录的变化&#xff0c;发生变化后通过rsync进行同步到备节点&#xf…

【北亚服务器数据恢复】san环境下LUN Mapping出错导致文件系统一致性出错的数据恢复案例

服务器数据恢复环境&#xff1a; san环境下的存储上一组由6块硬盘组建的RAID6&#xff0c;划分为若干LUN&#xff0c;MAP到跑不同业务的服务器上&#xff0c;服务器上层是SOLARIS操作系统UFS文件系统。 服务器故障&#xff1a; 业务需求需要增加一台服务器跑新增的应用&#xf…

如何实现h5的抽奖功能

示例: 使用的是一个插件可以在多种环境下运行的插件:九宫格 LuckyGrid | 基于 Js / TS / Vue / React / 微信小程序 / uni-app / Taro 的【大转盘 & 九宫格 & 老虎机】抽奖插件 安装插件 # npm 安装 npm install lucky-canvas/vuelatest# 或者 yarn 安装 yarn add lu…

秋招复习篇之代码规范

目录 前言 1、变量命名 2、代码空格 1&#xff09;操作符左右一定有空格&#xff0c; 2&#xff09;分隔符&#xff08;, 和;&#xff09;前一位没有空格&#xff0c;后一位保持空格&#xff0c;例如&#xff1a; 3&#xff09;大括号和函数保持同一行&#xff0c;并有一个空格…

复数值神经网络可能是深度学习的未来

一、说明 复数这种东西,在人的头脑中似乎抽象、似乎复杂,然而,对于计算机来说,一点也不抽象,不复杂,那么,将复数概念推广到神经网络会是什么结果呢?本篇介绍国外的一些同行的尝试实践,请我们注意观察他们的进展。

Frappe Charts:数据可视化的强大工具

一、产品简介&#xff1a; 一个简单、零依赖、响应式的 开源SVG 图表库。这个图表库无论是数据更新还是屏幕大小变化&#xff0c;都能快速响应并更新图表。数据生成和悬停查看都有舒服的交互动效&#xff0c;体验感很好。不仅支持配置颜色&#xff0c;外观定制也很方便。还支持…

MYSQL分库分表常见问题

什么是分库 分库&#xff1a;是将一个数据库分成多个数据库&#xff0c;但是总表数不变&#xff0c;部署到不同机器&#xff0c;这样可以分担数据库的读写压力&#xff0c;提高系统的吞吐能力。 什么是分表 分表&#xff1a;就是一个表分成多个表&#xff0c;但是总体的库不…

k8s之陈述式资源管理

1.kubectl命令 kubectl version 查看k8s的版本 kubectl api-resources 查看所有api的资源对象的名称 kubectl cluster-info 查看k8s的集群信息 kubectl get cs 查看master节点的状态 kubectl get pod 查看默认命名空间内的pod的信息 kubectl get ns 查看当前集群所有的命…

[Python进阶] 操作注册表:winreg

5.22 操作注册表&#xff1a;winreg 5.22.1 注册表概念 在Windows系统中&#xff0c;注册表本质上就是一个数据库&#xff0c;其中存放着硬件、软件、用户、操作系统以及程序相关设置信息。我们除了使用Windows系统自带的regedit程序&#xff0c;也可以通过Python编程读取并操…