小型命令解析器|minShell|多进程|重定向|进程控制【超详细的代码注释和解释】

 55b442c530014f30b6a3b4eda19d23ec.png


前言

那么这里博主先安利一下一些干货满满的专栏啦!

手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏https://blog.csdn.net/yu_cblog/category_11464817.htmlhttps://blog.csdn.net/yu_cblog/category_11464817.html这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。

STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482


什么是Shell

用百度百科的话讲:

在计算机科学中,Shell俗称壳(用来区别于核),是指“为使用者提供操作界面”的软件(command interpreter,命令解析器)。它类似于DOS下的COMMAND.COM和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序(摘自百度百科)。

小型命令解析器|minShell|多进程|重定向|进程控制【超详细的代码注释和解释】_第1张图片

 

用通俗的话讲:

shell就是我们写命令的地方,其实它的本质也是一个进程,名为bash。

那么我们可以来模拟实现一下这个东西。 


模拟实现代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


// shell运行原理: 通过让子进程执行命令,父进程等待和解析命令
// 为什么要子进程呢 -- 这样就算命令执行在子进程里面出错 -- 也不会影响父进程
// 因此我们不怕命令在命令行中出错
//


#define NUM 1024 // 定义一个大小为1024的缓冲区
char cmd_line[NUM]; //保存完整的命令行字符串
#define SIZE 32
char *g_argv[SIZE]; //保存拆分之后的命令行字符串

#define SEP " " //定义命令分隔符


#define INPUT_REDIR 1 //定义输入重定向
#define OUTPUT_REDIR 2 //定义输出重定向
#define APPEND_REDIR 3 //定义追加重定向
#define NONE_REDIR 0 //定义没有重定向

int redir_status = NONE_REDIR; //重定向类型

char* CheckRedir(char* start)
{
    assert(start);
    redir_status = NONE_REDIR;
    char* end = start + strlen(start)-1;//abcd\0
    //此时end是最后一个有效字符 -- 是\0的前一个
    while(end>=start)
    {
        if(*end == '>')
        {
            //ls -a -l>myfile.txt
            //ls -a -l>>myfile.txt
            //所以我们在前面要定义一下
            if(*(end-1) == '>')
            {
                //连续两个>  ----  追加重定向
                redir_status = APPEND_REDIR;
                *(end - 1) = '\0';
                end++;
                break;
            }
            //此时就是输出重定向
            *end = '\0';
            end++;
            redir_status = OUTPUT_REDIR;
            break;
        }
        else if(*end == '<')
        {
            //cat= start)
    {
        //提前break的
        return end;//要打开的文件的地址
    }
    else
    {
        return NULL;
    }
}
int main()
{
    //获取全局的环境变量
    extern char** environ;


    //0. 命令行解释器,一定是一个常驻内存的进程 -- 不退出
    while(1)
    {
        //1. 打印出提示信息 [yufc@localhost myshell]# 
        //  其实这一串是可以用系统接口来获取的 -- 不过我们不关心这些
        printf("[yufc@localhost myshell]# ");
        fflush(stdout);//手动刷新
        
        //2. 获取用户的键盘输入[输入指的是各种指令和选项]
        // 要输入 -- 我们就要提供一个输入的缓冲区
        memset(cmd_line,'\0',sizeof cmd_line); //sizeof不是函数 -- 是运算符, 所以可以不用()
        if(fgets(cmd_line,sizeof cmd_line, stdin) == NULL)
        {
            //表示没有在stdin里面获取命令时出错
            continue;
        }
        //此时要把cmd_line最后面的回车去掉
        cmd_line[strlen(cmd_line)-1] = '\0';
        //printf("echo: %s\n",cmd_line);
        
        //3. 拆分命令
        //"ls -a -l" ---> "ls" "-a" "-l"
        //strtok
        //第一次调用 -- 要传入原始字符串
        //如果还要继续解析原字符串 -- 传入NULL

        //把重定向也添加进来
        //ls -a -l>log.txt     今天我们先不去处理空格 -- 空格是特殊处理
        //ls -a -l>>log.txt
        //ls -a -llog.txt  --->  ls -a -l\0log.txt

        char* sep = CheckRedir(cmd_line);
        // printf("%p\n",sep);

        g_argv[0] = strtok(cmd_line,SEP);
        int idx = 1;
        //可以把ls的颜色加一下
        if(strcmp(g_argv[0],"ls") == 0)
        {
            g_argv[idx++] = "--color=auto";
        }
        while(g_argv[idx++] = strtok(NULL,SEP)); //这种写法 -- 如果返回NULL,子串提取完成
        //测试一下看看提取的对不对
        //for(idx = 0;g_argv[idx];idx++)
        //{
        //    printf("g_argv[%d]: %s\n",idx,g_argv[idx]);
        //}
        
        //4. TODO
        if(strcmp(g_argv[0],"cd")==0)
        {
            //让父进程执行 -- 不要创建子进程
            //内置命令(内建命令) -- 本质就是shell中的一个函数调用
            //我们用一个系统调用 -- chdir
            if(g_argv[1]!=NULL)chdir(g_argv[1]);
            continue;
        }
        //导入环境变量
        // if(strcmp(g_argv[0],"export") == 0 && g_argv[1]!=NULL)
        // {
        //     int ret = putenv(g_argv[1]);
        //     if(ret == 0) printf("%s export success\n",g_argv[1]);
        //     continue;
        // }

        //5. fork()
        pid_t id = fork();
        printf("开始重定向2 %d\n",id);
        if(id == 0) //child
        {
            if(sep != NULL)
            {
                int fd = -1;
                //说明命令曾经有重定向
                switch(redir_status)
                {
                    case INPUT_REDIR:
                        fd = open(sep, O_RDONLY);
                        dup2(fd, 0);
                        break;
                    case OUTPUT_REDIR:
                        fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
                        dup2(fd, 1);
                        break;
                    case APPEND_REDIR:
                        //TODO
                        fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
                        dup2(fd, 1);
                        break;
                    default:
                        printf("bug?\n");
                        break;
                }
            }
           // printf("下面功能让子进程进行的\n");
           // printf("child, MYVAL: %s\n", getenv("MYVAL"));
           // printf("child, PATH: %s\n", getenv("PATH"));
            //cd cmd , current child path
            //execvpe(g_argv[0], g_argv, environ); // ls -a -l -i
            //不是说好的程序替换会替换代码和数据吗??
            //环境变量相关的数据,会被替换吗??没有!
            execvp(g_argv[0], g_argv); // ls -a -l -i
            exit(1);
        }
        //父进程 -- 这里我们不用else了 -- 子进程执行完直接退出, 后面的肯定是父进程了
        int status = 0;
        pid_t ret = waitpid(id,&status,0);
        if(ret > 0)
        {
            printf("exit code: %d\n",WEXITSTATUS(status));
        }
    }
    return 0;
}

执行效果

这份代码只是在原理层面简单实现了shell,至于其他个性化的颜色等设置,我们可以继续添加配置。

小型命令解析器|minShell|多进程|重定向|进程控制【超详细的代码注释和解释】_第2张图片

在代码的实现过程,还添加了一些重定向信息,大家在用的时候可以屏蔽这部分代码。

你可能感兴趣的:(Linux,操作系统,linux,运维,服务器)