【Linux】自制shell

这篇具有很好参考价值的文章主要介绍了【Linux】自制shell。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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


目录

一、初始代码

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

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

3.1 chdir函数

四、关于环境变量的问题


 文章来源地址https://www.toymoban.com/news/detail-732457.html

一、初始代码

#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命令行程序。

运行效果:【Linux】自制shell,Linux,linux,运维,服务器

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

【Linux】自制shell,Linux,linux,运维,服务器

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

【Linux】自制shell,Linux,linux,运维,服务器

这是因为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);
    }
}

运行效果: 

【Linux】自制shell,Linux,linux,运维,服务器

 

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

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

【Linux】自制shell,Linux,linux,运维,服务器

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

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

 

3.1 chdir函数

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

【Linux】自制shell,Linux,linux,运维,服务器

我们可以向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);
    }
}

【Linux】自制shell,Linux,linux,运维,服务器

 

四、关于环境变量的问题

在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);
    }
}

运行结果: 

【Linux】自制shell,Linux,linux,运维,服务器

这张图不太好看,博主仔细找过,在子进程调用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);
    }
}

运行效果: 

【Linux】自制shell,Linux,linux,运维,服务器

但是这还不够,我们最终是用子进程调用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);
    }
}

运行效果: 

【Linux】自制shell,Linux,linux,运维,服务器

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


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

 

到了这里,关于【Linux】自制shell的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • shell脚本——服务器巡检(自动化运维)

     目的   自动 获取集群内 多个主机 的内存、磁盘、cpu等信息 生成日志  准备    VMware虚拟主机IP在同一个网段(互相能ping通)             虚拟主机都有公钥免登录            修改主机IP  vi/etc/sysconfig/netwoek-scripts/ifcfg-ens160            设置主机名 hostnamectl set-ho

    2024年02月15日
    浏览(19)
  • Linux服务器常见运维性能测试(1)综合跑分unixbench、superbench

    Linux服务器常见运维性能测试(1)综合跑分unixbench、superbench

    最近需要测试一批服务器的相关硬件性能,以及在常规环境下的硬件运行稳定情况,需要持续拷机测试稳定性。所以找了一些测试用例。本次测试包括在服务器的高低温下性能记录及压力测试,高低电压下性能记录及压力测试,常规环境下CPU满载稳定运行的功率记录。 这个系

    2024年02月04日
    浏览(53)
  • Linux本地部署1Panel服务器运维管理面板并实现公网访问

    Linux本地部署1Panel服务器运维管理面板并实现公网访问

    1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等 下面我们介绍在Linux 本地安装1Panel 并结合cpolar 内网穿透工具实现远程访问1Panel 管理界面 执行如下命令一键安装 1Panel: 安

    2024年02月04日
    浏览(55)
  • 【Linux】在服务器上创建Crontab(定时任务),自动执行shell脚本

    【Linux】在服务器上创建Crontab(定时任务),自动执行shell脚本

    业务场景:该文即为上次编写shell脚本的姊妹篇,在上文基础上,将可执行的脚本通过linux的定时任务自动执行,节省人力物力,话不多说,开始操作! 连上服务器后,在任意位置都可以执行: crontab -e 如果没有进入 编辑cron任务模式 根据提示查看我们的服务器上是否未安装crontab没有则

    2024年02月14日
    浏览(13)
  • [1Panel]开源,现代化,新一代的 Linux 服务器运维管理面板

    [1Panel]开源,现代化,新一代的 Linux 服务器运维管理面板

    本期测评试用一下1Panel这款面板。1Panel是国内飞致云旗下开源产品。整个界面简洁清爽,后端使用GO开发,前端使用VUE的Element-Plus作为UI框架,整个面板的管理都是基于docker的,想法很先进。官方还提供了视频的使用教程,本期为大家按照本专栏的基本内容进行多方面的测评。

    2024年02月07日
    浏览(34)
  • Java利用JSCH库远程连接Linux服务器以及操作多条Shell命令

    Java利用JSCH库远程连接Linux服务器以及操作多条Shell命令

    为了实现 DHCP 服务器的 IP 存储、回收和查询机制,我们需要将服务器中 LXD 容器的网卡 IP 查询出来,并且存储到服务器中的 Mysql 中。所以,下面介绍如何通过 Java 远程连接 Linux 服务器以及操作多条 Shell 命令。 创建 Maven 项目,导入依赖 jsch 实现远程连接,往后我们只需要调

    2024年02月13日
    浏览(15)
  • Linux服务器常见运维性能测试(3)CPU测试super_pi、sysbench

    Linux服务器常见运维性能测试(3)CPU测试super_pi、sysbench

    最近需要测试一批服务器的相关硬件性能,以及在常规环境下的硬件运行稳定情况,需要持续拷机测试稳定性。所以找了一些测试用例。本次测试包括在服务器的高低温下性能记录及压力测试,高低电压下性能记录及压力测试,常规环境下CPU满载稳定运行的功率记录。 这个系

    2024年02月02日
    浏览(33)
  • 华为云云耀云服务器L实例评测 | Linux系统宝塔运维部署H5游戏

    华为云云耀云服务器L实例评测 | Linux系统宝塔运维部署H5游戏

    本章节内容,我们主要介绍华为云耀服务器L实例,从云服务的优势讲起,然后讲解华为云耀服务器L实例资源面板如何操作,如何使用宝塔运维服务,如何使用运维工具可视化安装nginx,最后部署一个自研的H5的小游戏(6岁的小朋友玩的很开心😁)。 前端的同学如果想把自己

    2024年02月07日
    浏览(15)
  • Linux之实现Apache服务器监控、数据库定时备份及通过使用Shell脚本发送邮件

    Linux之实现Apache服务器监控、数据库定时备份及通过使用Shell脚本发送邮件

    目录  一、Apache服务器监控 为什么要用到服务监控? 实现Apache服务器监控 二、数据库备份 为什么要用到数据库备份? 实现数据库备份 三、Shell脚本发送邮件 为什么要用使用Shell脚本发送邮件? 实现Shell脚本发送邮件 在Linux中监控Apache服务器是非常重要的,原因如下: 保证

    2024年04月15日
    浏览(35)
  • 【Linux技术专题】「夯实基本功系列」带你一同学习和实践操作Linux服务器必学的Shell指令(排查问题指令 - 上)

    【Linux技术专题】「夯实基本功系列」带你一同学习和实践操作Linux服务器必学的Shell指令(排查问题指令 - 上)

    在线上排查问题时,查询日志、查看系统配置和分析操作系统信息是至关重要的。这些操作可以帮助我们深入了解软件和服务的兼容性,并解决潜在的问题。在本次学习中,我们将介绍并深入学习一些我在处理类似问题时常用的指令。通过掌握这些指令,你将能够更加高效地

    2024年01月16日
    浏览(21)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包