Linux:自定义Shell

        本文旨在通过自己完成一个简单的Shell来帮助理解命令行Shell这个程序。

目录

一、输出“提示”

二、获取输入

三、切割字符串

 四、执行指令

1.子进程替换

2.内建指令


一、输出“提示”

        这个项目基于虚拟机Ubuntu22.04.5实现。

        

        打开终端界面如图所示。

        其中。

@之前:utocoo是用户名
@之后:utocoo-virtul-machine是主机名
":"之后是当前路径,"~"表示用户家目录
"$"是普通用户的提示符,如果是root用户,则为"#"
光标闪烁位置在等待输入

         当前的用户名主机名当前工作目录这些信息都有对应的环境变量,故可以利用getenv拿到对应的值。

#include <stdio.h>
#include <stdlib.h>
//获取用户名
const char* UserName()
{const char* username = getenv("USER");if(username)return username;elsereturn "None";
}
//获取主机名
const char* HostName()
{const char* hostname = getenv("HOSTNAME");if(hostname)return hostname;elsereturn "None";
}
//获取目录
const char* CurrentWorkDir()
{const char* cwd = getenv("PWD");if(cwd)return cwd;else return "None";
}
int main()
{printf("%s@%s:%s$",UserName(),HostName(),CurrentWorkDir());return 0;
}

二、获取输入

        用户的输入是作为一个字符串被输入,故需要定义一个数组作为缓冲区。

        使用scanf输入时,遇到空格则会刷新缓冲区,故推荐使用fgets函数作为输入函数。关于C语言的各组输入函数,这篇文章做了很好的说明。https://blog.csdn.net/qq_53139964/article/details/142820767 

        补全其他板块的代码完成“获取输入”这一步骤。

#include <stdio.h>
#include <stdlib.h>#define SIZE 1024 //定义缓冲区数组大小const char* HostName()
{const char* hostname = getenv("HOSTNAME");if(hostname)return hostname;elsereturn "None";
}
const char* UserName()
{const char* username = getenv("USER");if(username)return username;elsereturn "None";
}
const char* CurrentWorkDir()
{const char* cwd = getenv("PWD");if(cwd)return cwd;else return "None";
}
int main()
{char commandline[SIZE];printf("%s@%s:%s$ ",UserName(),HostName(),CurrentWorkDir());//获取用户输入fgets(commandline,SIZE,stdin);printf("test:%s\n",commandline);return 0;
}

        测试结果如下。

        不难看出,打印结果中有两次“换行操作”。原因是fgets在stdin流中读取一定数量的信息时,会将我们自己输入的'\n'也读取进来,因此需要在commandline数组中去掉这个字符。

commandline[strlen(commandline)-1] = 0;//将'\n'修改为'0'

        封装处理后。

//命令行交互
void Interactive(char* out,int size)
{	printf("%s@%s:%s$ ",UserName(),HostName(),CurrentWorkDir());//获取用户输入fgets(out,size,stdin);out[strlen(out)-1] = 0;//将'\n'修改为'0'
}

三、切割字符串

        我们知道,命令行也是正在运行的程序,而在命令行执行输入的指令,其实是命令行这个进程创建子程序后再做程序替换,注意程序替换时,传参方式要么是可变参数,要么是指针数组,因此,无论如何,都要先将当前的字符串按照空格切割成一个个的子串,如果是指针数组的形式,要以NULL结尾。

        切割字符串可以利用C语言的字符串处理函数strtok

#define MAX_ARGC 64
#define SPC " "
char* argv[MAX_ARGC];//切割字符串
void Split(char* in)
{int i = 0;argv[i++] = strtok(in,SPC);while(argv[i++] = strtok(NULL,SPC));
}

        在这里,切割字符串的语句是这样一句while循环,循环体为空。仅仅这样一行代码就可以实现我们对字符串切割的要求,因为argv数组要求要以NULL结尾,而这句赋值语句将最后一个字符'\0'赋值给数组元素后,数组尾的数据就是NULL,同时表达式的值也为假,跳出循环。

 四、执行指令

1.子进程替换

        我们当前做的所有工作都是模拟shell这个程序,而模拟的命令行要执行我们输入的指令,必然要通过程序替换来完成,但是不能用shell这个进程做替换,应该创建子进程,让子进程做程序替换,父进程等待子进程。

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>//3.执行命令
pid_t id = fork();
if(id == 0)
{//程序替换,子进程执行命令exit(1);
}pid_t rid = waitpid(id,Null,0);
printf("run done!:%d",rid);
return 0;

        程序替换时,选择适当的替换函数也是很重要的,我们在模拟的时候是创建了argv数组,故选择带v的exec函数,其次,带p的exec函数可以不用指定系统指令的全部路径,故选择execvp这个函数。

//执行指令
void Execute()
{pid_t id = fork();if(id == 0){//程序替换,子进程执行命令execvp(argv[0],argv);exit(1);}pid_t rid = waitpid(id,NULL,0);printf("run done!:%d\n",rid);
}

        

        由于我的Ubuntu系统当前的环境变量没有主机名这个变量,因此主机名结果显示了None。

        但是,当前的shell只能执行一次程序替换,所以需要加上死循环,让shell一直运行。

int main()
{while(1){char commandline[SIZE];//1.打印命令行信息Interactive(commandline,SIZE);//2.切割字符串Split(commandline);//3.执行命令Execute();}return 0;
}

2.内建指令

        但是有些指令的执行结果是不符合预期的,比如cd指令这部分指令称为内建指令,具体请看这篇文章。https://blog.csdn.net/chen1415886044/article/details/103015950

        基于这一点,我们模拟的shell程序在执行cd指令的时候,其实是子程序替换为cd 指令,子程序执行了cd指令,路径发生改变的仅仅是子程序的路径,而我们平时在命令行所打印出来的路径,其实都是shell程序的路径,运行结果当然不符合预期。

        因此,在子程序替换执行指令之前,先判断要执行的指令是否要内建指令,如果是内建指令,则不需要创建子进程做替换,而是shell这个进程直接执行。

int main()
{while(1){char commandline[SIZE];//1.打印命令行信息Interactive(commandline,SIZE);//2.切割字符串Split(commandline);//3.执行内建指令int i = BuildinCmd();if(i) continue;//4.执行命令Execute();}return 0;
}
/执行内建指令
int BuildinCmd()
{//判断是否为内建指令,如果是,则返回1,否则返回0//并且执行内建指令//此处只列举cd这一条内建指令int ret = 0;if(strcmp("cd",argv[0])== 0){// cd *** :cd到具体路径 // cd 空 :cd到家目录char* Target = argv[1];if(!Target) Target = getenv("HOME");chdir(Target);ret = 1;}return ret;
}

         执行结果。

         在执行结果中,依旧有两个错误。

        1.输入为空,结果是段错误。

        2.cd指令执行后,命令行的输出提示中路径并未发生改变。


        要解决输入为空后发生的段错误,可以在交互的函数中返回输入字符串的长度,然后做if条件判断,特殊处理。

   


        至于在执行cd指令后,显示结果的路径并未发生改变,原因就是显示结果是由getenv得到,而此时的环境变量PWD并没有发生改变,因为当前myshell进程所在路径没有发生改变

        同时,不难总结出来,cd指令的执行和环境变量PWD的value息息相关

        可以利用snprintf这个函数,将格式化信息输出到指定大小的pwd字符串中,再利用putenv导入环境变量,则myshell程序就模拟出修改环境变量PWD的效果了

        putenv,导出环境变量,新建或者修改一个环境变量,如果putenv的参数是新的环境变量,则新建,如果是已经存在的环境变量,则修改。

        snprintf,和printf是一类函数,printf默认把格式化信息输出到屏幕,而多加了s (string)和n(表示字符串的大小)的snprintf表示把格式化信息输出到n长度的字符串中。

//关联环境变量,定义一个字符串,或者字符数组
char pwd[SIZE];
//执行内建指令
int BuildinCmd()
{//判断是否为内建指令,如果是,则返回1,否则返回0//并且执行内建指令//此处只列举cd这一条内建指令int ret = 0;if(strcmp("cd",argv[0])== 0){// cd *** | cd char* Target = argv[1];if(!Target) Target = getenv("HOME");chdir(Target);//关联环境变量,格式化信息会输出到pwd字符数组中snprintf(pwd,SIZE,"PWD=%s",Target);putenv(pwd);ret = 1;}return ret;
}

        但是在用cd指令执行下面这样的情况后,路径名并不达预期。

        原因是我们是使用Target来更新了环境变量,Target是我们输入的内容。

        可以利用getcwd函数获取当前进程的绝对路径再用getcwd的返回结果来更新环境变量

//执行内建指令
int BuildinCmd()
{//判断是否为内建指令,如果是,则返回1,否则返回0//并且执行内建指令//此处只列举cd这一条内建指令int ret = 0;if(strcmp("cd",argv[0])== 0){// cd *** | cd char* Target = argv[1];if(!Target) Target = getenv("HOME");chdir(Target);//关联环境变量,格式化信息会输出到pwd字符数组中char tmp[999];getcwd(tmp,999);snprintf(pwd,SIZE,"PWD=%s",tmp);putenv(pwd);ret = 1;}return ret;
}


      export指令也是内建指令,在export的指令被切割为argv数组后,argv数组的第二个元素就是要导入环境变量的字符串,可以直接putenv导入。

if(strcmp("export",argv[0])==0)
{ret = 1;if((argv[1]))putenv(argv[1]);
}

         随便导入一个环境变量,执行env命令后就能看到这个环境变量。但是在你执行一系列指令后,再执行env指令查看这个环境变量,可能会出现找不到的情况。

        原因就是,上面这段代码是通过argv数组导入的,在执行env指令,显示的时候指向了argv数组的值,而argv数组中的值在一次次执行指令的过程中会不断变换,因此已经导入的环境变量可能又会消失不见。

        正确做法是用数组保存要导入的环境变量。

//存储新的环境变量
char env[SIZE];if(strcmp("export",argv[0])==0)
{ret = 1;if((argv[1])){strcpy(env,argv[1]);putenv(env);}
}

         echo指令也是内建指令,执行echo指令一般有如下几种情况。

echo abcdef //输出一些信息
echo $HOME  //输出环境变量
echo $?     //输出上一次执行结果的进程退出码
echo        //输入echo后回车,结果会显示回车

        需要注意,在执行echo $?这个命令后,会显示上一条命令执行的退出码,如果再执行一次echo $?则显示结果应该为0。 

int lastExitCode = 0;
if(strcmp("echo",argv[0])==0){ret =1;if(argv[1]){if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n",lastExitCode);lastExitCode=0;//重置为0}else{char* tmp2 = getenv(argv[1]+1);if(tmp2) printf("%s\n",tmp2);else{printf("\n");}}}else{printf("%s\n",argv[1]);}}else{printf("\n");}}

        小细节,为ls指令添加颜色效果。

//切割字符串
void Split(char* in)
{int i = 0;argv[i++] = strtok(in,SPC);while(argv[i++] = strtok(NULL,SPC));if(strcmp("ls",argv[0]) == 0){argv[i-1] = (char*)"--color";argv[i] = NULL;}
}

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

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

相关文章

如何更改手机GPS定位

你是否曾想过更改手机GPS位置以保护隐私、玩游戏或访问受地理限制的内容&#xff1f;接下来我将向你展示如何使用 MagFone Location Changer 更改手机GPS 位置&#xff01;无论是在玩Pokmon GO游戏、发布社媒贴子&#xff0c;这种方法都快速、简单且有效。 第一步&#xff1a;下…

【AI大模型引领变革】探索AI如何重塑软件开发流程与未来趋势

文章目录 每日一句正能量前言流程与模式介绍【传统软件开发 VS AI参与的软件开发】一、传统软件开发流程与模式二、AI参与的软件开发流程与模式三、AI带来的不同之处 结论 AI在软件开发流程中的优势、挑战及应对策略AI在软件开发流程中的优势面临的挑战及应对策略 结论 后记 每…

Chapter 2 - 16. Understanding Congestion in Fibre Channel Fabrics

Transforming an I/O Operation to FC frames A read or write I/O operation (Figure 2-28) between an initiator and a target undergoes a series of transformations before being transmitted on a Fibre Channel link. 启动程序和目标程序之间的读取或写入 I/O 操作(图…

VMWARE虚拟交换机的负载平衡算法

一、基于源虚拟端口的路由 虚拟交换机可根据 vSphere 标准交换机或 vSphere Distributed Switch 上的虚拟机端口 ID 选择上行链路。 基于源虚拟端口的路由是 vSphere 标准交换机和 vSphere Distributed Switch 上的默认负载平衡方法。 ESXi主机上运行的每个虚拟机在虚拟交换…

web——sqliabs靶场——第十二关——(基于错误的双引号 POST 型字符型变形的注入)

判断注入类型 a OR 1 1# 发现没有报错 &#xff0c;说明单引号不是闭合类型 测试别的注入条件 a) OR 1 1# a)) OR 1 1# a" OR 11 发现可以用双引号闭合 发现是")闭合 之后的流程还是与11关一样 爆破显示位 先抓包 是post传参&#xff0c;用hackbar来传参 unam…

AI时代,百度的三大主义

现实主义、长期主义、理想主义。 定焦One&#xff08;dingjiaoone&#xff09;原创 作者 | 苏琦 郑浩钧 编辑 | 魏佳 “人工智能很像是一次新的工业革命&#xff0c;这意味着它不会三五年就结束&#xff0c;也不会一两年就出现‘超级应用’&#xff0c;它更像是三五十年对于整…

基于YOLOv11的火焰实时检测系统(python+pyside6界面+系统源码+可训练的数据集+也完成的训练模型)

100多种【基于YOLOv8/v10/v11的目标检测系统】目录&#xff08;pythonpyside6界面系统源码可训练的数据集也完成的训练模型 摘要&#xff1a; 本文提出了一种基于YOLOv11算法的火灾检测系统&#xff0c;利用1852张图片&#xff08;1647张训练集&#xff0c;205张验证集&#…

算法——两两交换链表中的节点(leetcode24)

这是一道对于链表节点进行操作的题目非常考验对于链表操作的基本功&#xff1b; 解法: 本题的解法结合下图来进一步解释 创建一个虚拟节点指向头结点以便使代码逻辑看起来更为简便且操作节点容易,定义cur是为了方便找到cur之后的两个节点进行交换操作定义pre和aft是为了保存执…

【提效工具开发】管理Python脚本执行系统实现页面展示

Python脚本执行&#xff1a;工具管理Python脚本执行系统 背景 在现代的软件开发和测试过程中&#xff0c;自动化工具和脚本的管理变得至关重要。为了更高效地管理工具、关联文件、提取执行参数并支持动态执行Python代码&#xff0c;我们设计并实现了一套基于Django框架的工具…

鸿蒙开发:ForEach中为什么键值生成函数很重要

前言 在列表组件使用的时候&#xff0c;如List、Grid、WaterFlow等&#xff0c;循环渲染时都会使用到ForEach或者LazyForEach&#xff0c;当然了&#xff0c;也有单独使用的场景&#xff0c;如下&#xff0c;一个很简单的列表组件使用&#xff0c;这种使用方式&#xff0c;在官…

Figma插件指南:12款提升设计生产力的插件

在当今的设计领域&#xff0c;Figma已经成为许多UI设计师和团队的首选原型和数字设计软件。随着Figma的不断更新和插件库的扩展&#xff0c;这些工具极大地提升了设计工作的效率。本文将介绍12款实用的Figma插件&#xff0c;帮助你在UI设计中更加高效。 即时AI 即时AI利用先进…

揭秘云计算 | 5、关于云计算效率的讨论

一、 公有云效率更高&#xff1f; 解&#xff1a;公有云具有更高的效率。首先我们需要知道效率到底指的是什么。这是个亟须澄清的概念。在这里效率是指云数据中心&#xff08;我们将在后文中介绍其定义&#xff09;中的IT设备资源利用率&#xff0c;其中最具有代表性的指标就是…

Flutter:AnimatedPadding动态修改padding

// 默认top为10&#xff0c;点击后修改为100&#xff0c;此时方块会向下移动 padding: EdgeInsets.fromLTRB(left, top, right, bottom),class _MyHomePageState extends State<MyHomePage> {bool flag true;overrideWidget build(BuildContext context) {return Scaffo…

【c++丨STL】stack和queue的使用及模拟实现

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C、STL 目录 前言 一、什么是容器适配器 二、stack的使用及模拟实现 1. stack的使用 empty size top push和pop swap 2. stack的模拟实现 三、queue的…

JavaEE初学07

JavaEE初学07 MybatisORMMybatis一对一结果映射一对多结果映射 Mybatis动态sqlif标签trim标签where标签set标签foreach标签补充 在这里插入图片描述右击运行即可 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c71d44d027374a399d5d537ce96f00e1.png) Mybatis Myba…

《Spring Cloud 微服务架构探秘》

一、Spring Cloud 微服务架构概述 Spring Cloud 是基于 Spring Boot 构建的微服务开发框架&#xff0c;它充分利用了 Spring Boot 的便利性&#xff0c;极大地简化了分布式系统基础设施的开发。 Spring Cloud 具有诸多显著特点。首先&#xff0c;它提供了丰富的组件&#xff0…

【AIGC】破解ChatGPT!如何使用高价值提示词Prompt提升响应质量

文章目录 为什么高价值提示词如此重要&#xff1f;&#x1f50d;1.1 提升响应的相关性和准确性1.2 节省时间与资源1.3 增强用户体验 了解ChatGPT的工作原理&#x1f9e0;2.1 语言模型的训练过程2.2 上下文理解与生成2.3 限制与挑战 高价值提示词的核心要素✍️3.1 清晰明确的指…

D74【 python 接口自动化学习】- python 基础之HTTP

day74 http基础定义 学习日期&#xff1a;20241120 学习目标&#xff1a;http定义及实战 -- http基础介绍 学习笔记&#xff1a; HTTP定义 HTTP 是一个协议&#xff08;服务器传输超文本到浏览器的传送协议&#xff09;&#xff0c;是基于 TCP/IP 通信协议来传递数据&…

2025 -生物信息学- GEO - 神经网络分析-ANN

GEO - 神经网络分析-ANN 01. 准备文件 id GSM3587381_con GSM3587382_con GSM3587383_con GSM3587384_con GSM3587385_con GSM3587386_con GSM3587387_con GSM3587388_con GSM3587389_con GSM3587390_con GSM3587391_con GSM3587392_con GSM3587393_con GSM3587394_con GSM358…

uniapp页面样式和布局和nvue教程详解

uniapp页面样式和布局和nvue教程 尺寸单位 uni-app 支持的通用 css 单位包括 px、rpx px 即屏幕像素。rpx 即响应式px&#xff0c;一种根据屏幕宽度自适应的动态单位。以750宽的屏幕为基准&#xff0c;750rpx恰好为屏幕宽度。屏幕变宽&#xff0c;rpx 实际显示效果会等比放大…