【Linux】自制shell

news2024/11/24 3:11:31

本期我们利用之前学过的知识,写一个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));//分割输入的命令行字符串,将每一个选项的地址存入指针数组s
        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);
    }
}

运行效果: 

三、解决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));//分割输入的命令行字符串,将每一个选项的地址存入指针数组s
        if (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 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
        if (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 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()
{
    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));//分割输入的命令行字符串,将每一个选项的地址存入指针数组s
        if (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 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;
    }
}
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));//分割输入的命令行字符串,将每一个选项的地址存入指针数组s
        if (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.coloradmin.cn/o/1004515.html

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

相关文章

第三节:在WORD为应用主窗口下关闭EXCEL的操作(2)

【分享成果&#xff0c;随喜正能量】凡事好坏&#xff0c;多半自作自受&#xff0c;既不是神为我们安排&#xff0c;也不是天意偏私袒护。业力之前&#xff0c;机会均等&#xff0c;毫无特殊例外&#xff1b;好坏与否&#xff0c;端看自己是否能应机把握&#xff0c;随缘得度。…

Pyspark案例综合(数据计算)

数据计算 map方法 map算子 map算子&#xff08;成员方法&#xff09;接受一个处理函数&#xff0c;可用lambda快速编写&#xff0c;对RDD内的元素一一处理&#xff0c;返回RDD对象 链式调用 对于返回值是新的RDD的算子&#xff0c;可以通过链式调用的方式多次调用算子 &q…

Windows Server操作系统概述

文章目录 一、计算机系统的层次结构二、五大基本功能1. 处理器管理2. 储蓄器管理3. 文件管理4. 设备管理5. 作业管理 三、应用场景四、发展历程1. Unix概述相关版本应用场景 2. Linux概述相关版本应用场景 3. windows概述普通版本服务器版本 首先可以看下思维导图&#xff0c;以…

Win10 NVIDIA Incompatible

Win10 NVIDIA 不兼容 https://www.nvidia.cn/Download/index.aspx?langcn https://www.nvidia.com/Download/Find.aspx?langen-us win10 version 1803

Python安装与环境变量配置傻瓜式教程(2023年9月)

给我家憨憨写的python教程 ——雁丘 Python的环境变量可以在安装包勾选自动配置&#xff0c;故相比Java相比简单不少 Python安装与环境变量配置傻瓜式教程&#xff08;2023年9月&#xff09; 一 Python的下载二 Python的安装三 手动配置环境变量四 检验环境变量 一 Python的下…

强化历程7-排序算法(2023.9.12)

此笔记学习图片来自于如下网址 1https://www.west999.com/info/html/chengxusheji/Javajishu/20190217/4612849.html 文章目录 强化历程7-排序算法1 冒泡排序(交换排序)2 选择排序3 直接插入排序4 希尔排序5 归并排序6 快速排序7 堆排序8 计数排序 强化历程7-排序算法 1 冒泡排…

《向量数据库指南》——向量数据库内核面临的技术挑战及应对措施

最近一年&#xff0c;以 ChatGPT、LLaMA 为代表的大语言模型的兴起&#xff0c;将向量数据库的发展推向了新的高度。 向量数据库是一种在机器学习和人工智能领域日益流行的新型数据库&#xff0c;它能够帮助支持基于神经网络而不是关键字的新型搜索引擎。向量数据库不同于传统的…

6.1、Flink数据写入到文件

1、前言 Flink API 提供了FileSink连接器&#xff0c;来帮助我们将数据写出到文件系统中去 版本说明&#xff1a;java1.8、flink1.17 官网链接&#xff1a;官网 2、Format Types - 指定文件格式 FileSink 支持 Row-encoded 、Bulk-encoded 两种格式写入文件系统 Row-encode…

小程序自定义tabbar

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

SpringWeb解析

目录 运行流程 组件介绍 简单搭建一个SpringWeb项目 1.导入依赖 2.配置DispatcherServlet 3.开启注解 4.处理器搭建 5.接收请求 获取请求的数据 中文乱码 返回json格式 拦截器 代码实现 SpringWeb 是 spring 框架中的一个模块&#xff0c;基于 Servlet API 构建的原…

豆瓣图书评分数据的可视化分析

导语 豆瓣是一个提供图书、电影、音乐等文化产品的社区平台&#xff0c;用户可以在上面发表自己的评价和评论&#xff0c;形成一个丰富的文化数据库。本文将介绍如何使用爬虫技术获取豆瓣图书的评分数据&#xff0c;并进行可视化分析&#xff0c;探索不同类型、不同年代、不同…

如何做好医药产品说明书翻译?

近年来&#xff0c;随着世界各国之间的交流日渐紧密&#xff0c;医药产业发达国家的药品和医用器械对于其他国家的输出日益增多&#xff0c;但这些医疗产品在流通过程中&#xff0c;往往需要专业的医药翻译人员进行产品说明书的精确翻译。那么&#xff0c;如何做好医药产品说明…

掌动智能:提升硬件连通性测试效率与精确度

在当今数字化时代&#xff0c;各种智能设备和硬件组件在我们的日常生活和工作中扮演着越来越重要的角色。为确保这些设备正常运行&#xff0c;并实现跨设备的无缝连接&#xff0c;硬件连通性测试变得至关重要。为了提高测试效率与精确度&#xff0c;让我们介绍掌动智能作为一家…

物理层(408)

一、通信基础 【2009】在无噪声的情况下&#xff0c;若某通信链路的带宽为3kHz&#xff0c;采用4个相位&#xff0c;每个相位具有4种振幅的QAM调制技术&#xff0c;则该通信链路的最大数据传输速率是&#xff08;B&#xff09; A、12kb/s B、24kb/s C、48kb/s …

A股风格因子看板 (2023.09 第01期)

该因子看板跟踪A股风格因子&#xff0c;该因子主要解释沪深两市的市场收益、刻画市场风格趋势的系列风格因子&#xff0c;用以分析市场风格切换、组合风格暴 露等。 今日为该因子跟踪第1期&#xff0c;指数组合数据截止日2023-08-31&#xff0c;要点如下 近1年A股风格因子收益走…

《DevOps实践指南》- 读书笔记(五)

DevOps实践指南 Part 4 第二步 &#xff1a;反馈的技术实践14. 建立能发现并解决问题的遥测系统14.1 建设集中式监控架构14.2 建立生产环境的应用程序日志遥测14.3 使用遥测指导问题的解决14.4 将建立生产遥测融入日常工作14.5 建立自助访问的遥测和信息辐射器14.6 发现和填补遥…

元宇宙全球市场规模到2030年将达9805亿美元!

元宇宙是一种新兴的概念&#xff0c;它指的是一个虚拟的世界&#xff0c;由人工智能、虚拟现实、区块链等技术构建而成。元宇宙的起源可以追溯到上世纪90年代的虚拟世界“第二人生”&#xff0c;但直到近年来&#xff0c;随着技术的不断发展&#xff0c;它才逐渐成为了人们关注…

labelme2voc 标签重叠/覆盖问题

使用labelme自带的 labelme2voc.py转换voc数据集时可能标签重叠

C语言实现单链表和双向循环链表

全文目录 链表单链表实现申请节点头插尾插头删尾删任意节点后插入删除单链表的销毁 带头双向循环链表实现链表初始化申请节点头插尾插头删尾删任意节点后插入删除链表的销毁 链表和顺序表对比总结 链表 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&…

GO语言篇之发布开源软件包

GO语言篇之发布开源软件包 文章目录 GO语言篇之发布开源软件包新建仓库拉取到本地初始化项目编写代码提交代码发布引用软件包 我们写GO语言程序的时候难免会引用第三方的软件包&#xff0c;那么你知道别人是怎么发布自己的软件包吗&#xff0c;别急&#xff0c;这篇博客教你怎么…