【Linux】自制shell

本期我们利用之前学过的知识,写一个shell命令行程序


目录

一、初始代码

二、使用户输入的ls指令带有颜色分类

三、解决cd指令后用户所在路径不变化问题

3.1 chdir函数

四、关于环境变量的问题


一、初始代码

#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
#include<assert.h>    
#include<string.h>    
#include<sys/wait.h>    #define MAX 1024    
#define ARGc 512    void shift_commend(char** s, char* c, int n)
{assert(s);assert(c);int num = 0;*s = c;++s;while (num < n){if (*(c + num) == ' ')//将每个空格字符替换为'\0',再将选项的首地址存入s{*(c + num) = '\0';if (*(c + num + 1) != ' ')//防止用户输入多个空字符{*s = c + 1 + num;++s;}}++num;}
}
int main()
{while (1){printf("[%s@MyShell]#", getenv("USER"));fflush(stdout);char comment[MAX] = { 0 };char* p = fgets(comment, sizeof(comment), stdin);assert(p);//assert函数在release版本下会被编译器删去    (void)p;//防止在release版本下assert函数被删除导致p指针从未被使用而报错    comment[strlen(comment) - 1] = '\0';//去除fgets函数最后获取的\n字符    char* s[ARGc] = { NULL };shift_commend(s, comment, strlen(comment));//分割输入的命令行字符串,将每一个选项的地址存入指针数组s    pid_t id = fork();if (id == 0){execvp(*s, s);exit(1);}int status = 0;waitpid(id, &status, 0);}
}

上述代码我们实现了一个基本的shell命令行程序,其实现基本思路为:

使用fgets函数获取用户输入的命令行,获取后切除字符串最后的换行符(用户输入指令后必须使用换行符来输入缓冲区中,而fgets并不会自动切除其换行符),然后将输入的指令进行切割(将指令与选项一个个切割开,方便传入execvp函数(例如fgets函数获取到用户输入的“ls -a -l”,经过切割会变成“ls/0-a/0-l/0”))。最后再创建子进程,用子进程调用execvp函数,传入用户指令最后实现shell命令行程序。

运行效果:

我们拿xshell来对比一下我们自己实现的shell命令行:

咦?xshell的ls指令有颜色变化,为什么我们自己实现的shell就没有呢?

这是因为xshell下的ls指令多了一行演示配置:--color=auto

如果我们要想自己的shell命令行的ls指令有颜色分类的话,我们只需要在切割用户输入的指令后判断其是否为ls指令,如果是,在该指针数组的最后一项加上"--color=auto"字符串即可:

二、使用户输入的ls指令带有颜色分类

#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
#include<assert.h>    
#include<string.h>    
#include<sys/wait.h>    #define MAX 1024    
#define ARGc 512    void shift_commend(char** s, char* c, int n)
{assert(s);assert(c);int num = 0;*s = c;++s;while (num < n){if (*(c + num) == ' ')//将每个空格字符替换为'\0',再将选项的首地址存入s{*(c + num) = '\0';if (*(c + num + 1) != ' ')//防止用户输入多个空字符{*s = c + 1 + num;++s;}}++num;}
}
int main()
{while (1){printf("[%s@MyShell]#", getenv("USER"));fflush(stdout);char comment[MAX] = { 0 };char* p = fgets(comment, sizeof(comment), stdin);assert(p);//assert函数在release版本下会被编译器删去    (void)p;//防止在release版本下assert函数被删除导致p指针从未被使用而报错    comment[strlen(comment) - 1] = '\0';//去除fgets函数最后获取的\n字符    char* s[ARGc] = { NULL };shift_commend(s, comment, strlen(comment));//分割输入的命令行字符串,将每一个选项的地址存入指针数组sif (strcmp(s[0], "ls") == 0)//使用户输入的ls指令带有颜色分类{int n = 0;while (*(s + n)){++n;}*(s + n) = (char*)"--color=auto";}pid_t id = fork();if (id == 0){execvp(*s, s);exit(1);}int status = 0;waitpid(id, &status, 0);}
}

运行效果: 

三、解决cd指令后用户所在路径不变化问题

上面代码还有一个问题,在我们使用cd指令后,再使用pwd来查看所在路径时,会发现没有任何变化:

这是因为当用户输入cd指令后,通过子进程调用的execvp函数来执行cd指令,改变的是子进程的目录路径和父进程压根没有关系!

所以当类似cd这样要改变父进程环境的指令(内建指令),就要父进程自己来执行,我们需要特殊判断做特殊处理:

 

3.1 chdir函数

chdir函数(包含在头文件unistd.h中)可以改变进程所在路径:

我们可以向path传入想要进入的路径,这样子chdir函数就会自动帮我们改变当前进程的所处路径:

#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
#include<assert.h>    
#include<string.h>    
#include<sys/wait.h>    #define MAX 1024    
#define ARGc 512    void shift_commend(char** s, char* c, int n)
{assert(s);assert(c);int num = 0;*s = c;++s;while (num < n){if (*(c + num) == ' ')//将每个空格字符替换为'\0',再将选项的首地址存入s{*(c + num) = '\0';if (*(c + num + 1) != ' ')//防止用户输入多个空字符{*s = c + 1 + num;++s;}}++num;}
}
int main()
{while (1){printf("[%s@MyShell]#", getenv("USER"));fflush(stdout);char comment[MAX] = { 0 };char* p = fgets(comment, sizeof(comment), stdin);assert(p);//assert函数在release版本下会被编译器删去    (void)p;//防止在release版本下assert函数被删除导致p指针从未被使用而报错    comment[strlen(comment) - 1] = '\0';//去除fgets函数最后获取的\n字符    char* s[ARGc] = { NULL };shift_commend(s, comment, strlen(comment));//分割输入的命令行字符串,将每一个选项的地址存入指针数组sif (strcmp(s[0], "cd") == 0)//cd指令直接使用chdir函数改变父进程所在路径{if(s[1]!=NULL)chdir(s[1]);continue;}if (strcmp(s[0], "ls") == 0)//使用户输入的ls指令带有颜色分类{int n = 0;while (*(s + n)){++n;}*(s + n) = (char*)"--color=auto";}pid_t id = fork();if (id == 0){execvp(*s, s);exit(1);}int status = 0;waitpid(id, &status, 0);}
}

四、关于环境变量的问题

在xshell中我们可以使用export导入自定义的环境变量,但是在我们自实现的代码中还没有这个功能,下面我们来实现一下:

使用我们在往期博客中介绍过导入环境变量的函数:putenv

#include<stdio.h>                                                                                                                                                                                                   
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<sys/wait.h>#define MAX 1024
#define ARGc 512void shift_commend(char** s, char* c, int n)
{assert(s);assert(c);int num = 0;*s = c;++s;while (num < n){if (*(c + num) == ' ')//将每个空格字符替换为'\0',再将选项的首地址存入s{*(c + num) = '\0';if (*(c + num + 1) != ' ')//防止用户输入多个空字符{*s = c + 1 + num;++s;}}++num;}
}
int main()
{while (1){printf("[%s@MyShell]#", getenv("USER"));fflush(stdout);char comment[MAX] = { 0 };char* p = fgets(comment, sizeof(comment), stdin);assert(p);//assert函数在release版本下会被编译器删去    (void)p;//防止在release版本下assert函数被删除导致p指针从未被使用而报错    comment[strlen(comment) - 1] = '\0';//去除fgets函数最后获取的\n字符    char* s[ARGc] = { NULL };shift_commend(s, comment, strlen(comment));//分割输入的命令行字符串,将每一个选项的地址存入指针数组sif (strcmp(s[0], "cd") == 0)//cd指令直接使用chdir函数改变父进程所在路径{if(s[1]!=NULL)chdir(s[1]);continue;}if (strcmp(s[0], "export") == 0)//导入用户自定义的环境变量{if (s[1] != NULL)putenv(s[1]);continue;}if (strcmp(s[0], "ls") == 0)//使用户输入的ls指令带有颜色分类{int n = 0;while (*(s + n)){++n;}*(s + n) = (char*)"--color=auto";}pid_t id = fork();if (id == 0){execvp(*s, s);exit(1);}int status = 0;waitpid(id, &status, 0);}
}

运行结果: 

这张图不太好看,博主仔细找过,在子进程调用env指令查找环境变量时,并没有看到导入的环境变量“Myenv=100”

这是为什么呢?子进程的环境变量不应该继承父进程的嘛?

这点没错,子进程确实基础了父进程的环境变量,我们再来仔细看看可以看到,在我们导入环境变量后,再一次调用env指令,屏幕上在最后多打印了一行空行。为什么会这样?

因为使用putenv函数导入环境变量时,该函数只是把形参所获取到的地址添加到进程中的环境变量表中了,我们仔细看看代码中存储环境变量的是一个变量s[1],当我们下一次在调用其他指令时该地址的数据就发生了变化!也就是说自定义环境变量是由我们自己维护的,我们应该将其放在一个不会被覆盖的空间里

下面我们改写一下代码:

#include<stdio.h>                                                                                                                                                                                                   
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<sys/wait.h>#define MAX 1024
#define ARGc 512void shift_commend(char** s, char* c, int n)
{assert(s);assert(c);int num = 0;*s = c;++s;while (num < n){if (*(c + num) == ' ')//将每个空格字符替换为'\0',再将选项的首地址存入s{*(c + num) = '\0';if (*(c + num + 1) != ' ')//防止用户输入多个空字符{*s = c + 1 + num;++s;}}++num;}
}
int main()
{char user_env[32][256];//存储自定义环境变量int env_num = 0;while (1){printf("[%s@MyShell]#", getenv("USER"));fflush(stdout);char comment[MAX] = { 0 };char* p = fgets(comment, sizeof(comment), stdin);assert(p);//assert函数在release版本下会被编译器删去    (void)p;//防止在release版本下assert函数被删除导致p指针从未被使用而报错    comment[strlen(comment) - 1] = '\0';//去除fgets函数最后获取的\n字符    char* s[ARGc] = { NULL };shift_commend(s, comment, strlen(comment));//分割输入的命令行字符串,将每一个选项的地址存入指针数组sif (strcmp(s[0], "cd") == 0)//cd指令直接使用chdir函数改变父进程所在路径{if(s[1]!=NULL)chdir(s[1]);continue;}if (strcmp(s[0], "export") == 0)//导入用户自定义的环境变量{if (s[1] != NULL){strcpy(*user_env, s[1]);//将用户输入的环境变量复制到自定义存储空间里putenv(user_env[env_num++]);}continue;}if (strcmp(s[0], "ls") == 0)//使用户输入的ls指令带有颜色分类{int n = 0;while (*(s + n)){++n;}*(s + n) = (char*)"--color=auto";}pid_t id = fork();if (id == 0){execvp(*s, s);exit(1);}int status = 0;waitpid(id, &status, 0);}
}

运行效果: 

但是这还不够,我们最终是用子进程调用env来打印环境变量的,但是不排除子进程因为要进行某些操作会修改环境变量,所以最保险的方法还是自己实现一个打印父进程的环境变量的函数:

void show_env()
{extern char** environ;//使用environ前先声明  int i = 0;for (i; environ[i]; ++i){printf("environ[%d]:%s\n", i, environ[i]);}
}

检测到env指令时调用一下该函数即可:

#include<stdio.h>                                                                                                                                                                                                   
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<sys/wait.h>#define MAX 1024
#define ARGc 512void shift_commend(char** s, char* c, int n)
{assert(s);assert(c);int num = 0;*s = c;++s;while (num < n){if (*(c + num) == ' ')//将每个空格字符替换为'\0',再将选项的首地址存入s{*(c + num) = '\0';if (*(c + num + 1) != ' ')//防止用户输入多个空字符{*s = c + 1 + num;++s;}}++num;}
}
void show_env()
{extern char** environ;//使用environ前先声明  int i = 0;for (i; environ[i]; ++i){printf("environ[%d]:%s\n", i, environ[i]);}
}
int main()
{char user_env[32][256];//存储自定义环境变量int env_num = 0;while (1){printf("[%s@MyShell]#", getenv("USER"));fflush(stdout);char comment[MAX] = { 0 };char* p = fgets(comment, sizeof(comment), stdin);assert(p);//assert函数在release版本下会被编译器删去    (void)p;//防止在release版本下assert函数被删除导致p指针从未被使用而报错    comment[strlen(comment) - 1] = '\0';//去除fgets函数最后获取的\n字符    char* s[ARGc] = { NULL };shift_commend(s, comment, strlen(comment));//分割输入的命令行字符串,将每一个选项的地址存入指针数组sif (strcmp(s[0], "cd") == 0)//cd指令直接使用chdir函数改变父进程所在路径{if(s[1]!=NULL)chdir(s[1]);continue;}if (strcmp(s[0], "export") == 0)//导入用户自定义的环境变量{if (s[1] != NULL){strcpy(*user_env, s[1]);//将用户输入的环境变量复制到自定义存储空间里putenv(user_env[env_num++]);}continue;}if (strcmp(s[0], "env") == 0)//直接打印父进程环境变量{show_env();continue;}if (strcmp(s[0], "ls") == 0)//使用户输入的ls指令带有颜色分类{int n = 0;while (*(s + n)){++n;}*(s + n) = (char*)"--color=auto";}pid_t id = fork();if (id == 0){execvp(*s, s);exit(1);}int status = 0;waitpid(id, &status, 0);}
}

运行效果: 

从上面的实现过程我们可以得出一个结论:其实我们之前学习到的几乎所有的环境变量命令,都是内建命令!


这就是本期博客的全部内容,如有纰漏还请各位大佬指点~

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

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

相关文章

LINUX 网络管理

目录 一、NetworkManager的特点 二、配置网络 1、使用ip命令临时配置 1&#xff09;查看网卡在网络层的配置信息 2&#xff09;查看网卡在数据链路层的配置信息 3&#xff09;添加或者删除临时的网卡 4&#xff09;禁用和启动指定网卡 2、修改配置文件 3、nmcli命令行…

软件安全研究(四)

文章目录 Fine-Grained Code Clone Detection with Block-Based Splitting of Abstract Syntax Tree文章结构IntroMotivationDefinitionSystemOverviewProcessingVerify Experimentexperimental settingsRQ1RQ2RQ3RQ4RQ5 Fine-Grained Code Clone Detection with Block-Based S…

78 # koa 中间件的实现

上上节实现了上下文的&#xff0c;上一节使用了一下中间件&#xff0c;这一节来实现 koa 的中间件这个洋葱模型。 思路&#xff1a; 储存用户所有的 callback将用户传递的 callback 全部组合起来&#xff08;redux 里的 compose&#xff09;组合成一个线性结构依次执行&#…

Vue3、Vite使用 html2canvas 把Html生成canvas转成图片并保存,以及填坑记录

这两天接到新需求就是生成海报分享&#xff0c;生成的格式虽然是一样的但是自己一点点画显然是不符合我摸鱼人的性格&#xff0c;就找到了html2canvas插件&#xff0c;开始动工。 安装 npm install html2canvas --save文档 options 的参数都在里面按照自己需求使用 https://a…

Union-Find Algorithm-并查集

目录 1.概念 2.并查集的优化 1.路径压缩&#xff08;Path Compression&#xff09; 1&#xff09;隔代压缩&#xff1a; 2&#xff09;完全压缩&#xff1a; 2.按秩合并 1.概念 并查集&#xff1a;用于判断一对元素是否相连&#xff0c;它们的关系是动态添加&#xff08…

nlp系列(7)实体识别(Bert)pytorch

模型介绍 本项目是使用Bert模型来进行文本的实体识别。 Bert模型介绍可以查看这篇文章&#xff1a;nlp系列&#xff08;2&#xff09;文本分类&#xff08;Bert&#xff09;pytorch_bert文本分类_牧子川的博客-CSDN博客 模型结构 Bert模型的模型结构&#xff1a; 数据介绍 …

骨传导耳机的危害有哪些?会损害听力吗?

如果正常的使用&#xff0c;骨传导耳机是没有危害的&#xff0c;由于骨传导耳机独特的传声方式&#xff0c;所以并不会对人体造成损伤&#xff0c;还可以在一定程度上保护听力。 如果想更具体知道骨传导耳机有什么危害&#xff0c;就要先了解什么是骨传导耳机&#xff0c;骨传…

小程序自定义tabbar

前言 使用小程序默认的tabbar可以满足常规开发&#xff0c;但是满足不了个性化需求&#xff0c;如果想个性化开发就需要用到自定义tabbar,以下图为例子 一、在app.json配置 先按照以往默认的形式配置&#xff0c;如果中间的样式特殊则不需要配置 "tabBar": {&qu…

来可LCWLAN-600P产品使用和常见问题说明

01LCWLAN-600P简介 LCWLAN-600P是来可电子最新生产的一款CAN转WiFi设备&#xff0c;该设备的主要功能是将CAN数据转换成网络数据并通过无线网络转发出去。设备支持8~30V宽压供电&#xff0c;出厂默认配置为AP模式&#xff0c;设备供电后可在电脑的WiFi搜索栏搜索到名称为LCWLA…

【Linux-Day10-信号量,共享内存,消息队列】

信号量 信号量描述 信号量是一个特殊的变量&#xff0c;一般取正数值。它的值代表允许访问的资源数目&#xff0c;获取资源 时&#xff0c;需要对信号量的值进行原子减一&#xff0c;该操作被称为 P 操作。 当信号量值为 0 时&#xff0c;代表没有资源可用&#xff0c;P 操作…

2022年全国研究生数学建模竞赛华为杯B题方形件组批优化问题求解全过程文档及程序

2022年全国研究生数学建模竞赛华为杯 B题 方形件组批优化问题 原题再现&#xff1a; 背景介绍   智能制造被“中国制造2025”列为主攻方向, 而个性化定制、更短的产品及系统生命周期、互联互通的服务模式等成为目前企业在智能制造转型中的主要竞争点。以离散行业中的产品为…

20230912java面经整理

1.gc算法有哪些 引用计数&#xff08;循环引用&#xff09;和可达性分析找到无用的对象 标记-清除&#xff1a;简单&#xff0c;内存碎片&#xff0c;大对象找不到空间 标记-复制&#xff1a;分成两半&#xff0c;清理一半&#xff0c;没有碎片&#xff0c;如果存活多效率低&a…

Python之离线安装第三方库

1、场景介绍 在一些服务器上&#xff0c;我们搭建完Python环境之后&#xff0c;因为服务器的网络限制原因&#xff0c;不能直接通过pip命令下载安装Python的依赖包。 因此&#xff0c;我们需要在可以正常上网的服务器上下载好所需的依赖包文件&#xff0c;然后拷贝到目标服务器…

嘉泰实业:真实低门槛,安全有保障

在互联网金融大行其道的当下&#xff0c;无论用户是多么的青睐、喜爱这种便捷的理财方式&#xff0c;也一定得把资金安全放在心上。要投就投那些实力背景雄厚&#xff0c;诚信经营的平台&#xff0c;可以选择投资用户基数庞大的理财老品牌&#xff0c;也可以选择发展势头迅猛的…

OneFormer: One Transformer to Rule Universal Image Segmentation论文笔记

论文https://arxiv.org/pdf/2211.06220.pdfCodehttps://github.com/SHI-Labs/OneFormer 文章目录 1. Motivation2. 方法2.1 与Mask2Former的相同之处2.2 OneFormer创新之处2.3 Task Conditioned Joint Training2.4 Query Representations2.4 Task Guided Contrastive Queries 3…

【Redis】为什么要学 Redis

文章目录 前言一、Redis 为什么快二、Redis 的特性2.1 将数据储存到内存中2.2 可编程性2.3 可扩展性2.4 持久性2.5 支持集群2.6 高可用性 三、Redis 的应用场景四、不能使用 Redis 的场景 前言 关于为什么要学 Redis 这个问题&#xff0c;一个字就可以回答&#xff0c;那就是&…

学习记忆——宫殿篇——记忆宫殿——数字编码——记忆数字知识点

面对错综复杂的数字信息&#xff0c;我们想要记住可以通过以下三点&#xff1a; 1、首先找到关键词 2、数字编码牢记 3、关键词跟编码链接 案例&#xff1a;会计考试-时间期限为 3、7、10 日、1 年的知识点 3 天 (1)托收承付的承付期验单付款为 3 天。 (2)失票人应当在通…

帝国cms后台访问链接提示“非法来源”解决方法

提示“非法来源”的原因 帝国CMS更新升级7.2后,新增了后台安全模式,后台推出了金刚模式来验证链接来源。后台所有链接都需要登录后才能访问,直接强制访问后台页面链接都会提示“非法来源”。不是正常登录后台的用户无法直接访问到内容,保证了后台数据安全。 那么我们在日常…

【设计模式】三、概述分类+单例模式

文章目录 概述设计模式类型 单例模式饿汉式&#xff08;静态常量&#xff09;饿汉式&#xff08;静态代码块&#xff09;懒汉式(线程不安全)懒汉式(线程安全&#xff0c;同步方法)懒汉式(线程安全&#xff0c;同步代码块)双重检查静态内部类枚举单例模式在 JDK 应用的源码分析 …

04-Redis哨兵高可用架构

上一篇&#xff1a;03-Redis主从架构 架构说明 sentinel哨兵是特殊的redis服务&#xff0c;不提供读写服务&#xff0c;主要用来监控redis实例节点。 哨兵架构下client端第一次从哨兵找出redis的主节点&#xff0c;后续就直接访问redis的主节点&#xff0c;不会每次都通过s…