• 模拟shell小程序


    接下来利用我们当前的知识,撰写一个简单的shell外壳程序。

    1.shell原理

    shell的原理是实际上就是运行了一个父进程,然后创建出子进程,最后使用进程替换调用,替换成其他程序。

    2.shell实现

    2.1.死循环

    首先一个shell一旦运行起来就不会关闭(除非关闭终端窗口),因此一定是一个死循环。

    int main()
    {
    	while(1)
        {
            //...
        }
        return 0}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.2.提示字符串

    运行一个shell有出现一个类似[user@myshell]$的输入提示符,由于不能换行,因此我们还需要使用fflush()进行刷新。

    #include 
    int main()
    {
    	while(1)
        {
            printf("[user@myshell]$ ");
            fflush(stdout);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.3.获取用户输入

    #include 
    #include 
    
    #define NUM 1024
    char cmd_line[NUM];//保存完整命令字符串
    
    int main()
    {
    	while(1)
        {
            //1.打印提示信息
            printf("[user@myshell]$ ");
            fflush(stdout);
        
            //2.获取 user 的输入
            memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
            if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串
            {
                //出错就直接跳出循环
                continue;
            }
            cmd_line[strlen(cmd_line) - 1] = '\0';//即使用户输入字符串超出范围,也能保证获取到一个完整的 C 风格字符串
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2.4.解析用户输入

    #include 
    #include 
    
    #define NUM 1024
    char cmd_line[NUM];//保存完整命令字符串
    
    #define SIZE 32
    char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组
    
    #define SEP " "//分隔符
    
    int main()
    {
    	while(1)
        {
            //1.打印提示信息
            printf("[user@myshell]$ ");
            fflush(stdout);
        
            //2.获取 user 的输入
            memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
            if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串
            {
                //出错就直接跳出循环
                continue;
            }
            cmd_line[strlen(cmd_line) - 1] = '\0';//即使用户输入字符串超出范围,也能保证获取到一个完整的 C 风格字符串
    
            //3.命令行字符解析
            //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
            g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符
    
            int index = 1;
            if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
            {
                g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
            }
            while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    2.5.创建并且替换子进程

    #include 
    #include 
    #include 
    #include 
    #include  
    #include 
    #include 
    
    #define NUM 1024
    char cmd_line[NUM];//保存完整命令字符串
    
    #define SIZE 32
    char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组
    
    #define SEP " "//分隔符
    
    int main()
    {
    	while(1)
        {
            //1.打印提示信息
            printf("[user@myshell]$ ");
            fflush(stdout);
        
            //2.获取 user 的输入
            memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
            if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串
            {
                //出错就直接跳出循环
                continue;
            }
            cmd_line[strlen(cmd_line) - 1] = '\0';//即使用户输入字符串超出范围,也能保证获取到一个完整的 C 风格字符串
    
            //3.命令行字符解析
            //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
            g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符
    
            int index = 1;
            if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
            {
                g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
            }
            while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
    
            //4.fork() 创建子进程,execvp() 替换子进程
            pid_t id = fork();
            if(id == 0)//child
            {
                printf("子进程 run:\n");
                execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
                exit(1);//没替换成功就会异常退出
            }
            //father
            int status;
            pid_t ret = waitpid(id, &status, 0);//阻塞等待
            if(ret > 0) 
                printf("exit code: %d\n", WEXITSTATUS(status));
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    2.6.debug

    有一个小小的bug我们没有解决,就是使用cd命令的时候,没有办法切入到对应的目录,这是为什么呢?

    3.shell拓展

    3.1.用户名、主机名、目录获取

    上面我们只是生硬显示了user充当用户名字,实际上我们可以通过环境变量获取执行shell的用户的名字,还可以获取在不同环境下的系统主机名字,使用这两个环境变量的内容填充到提示字符串中,目录也可以这样操作。

    #include 
    #include 
    #include 
    #include 
    #include  
    #include 
    #include 
    
    #define NUM 1024
    char cmd_line[NUM];//保存完整命令字符串
    
    #define SIZE 32
    char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组
    
    #define SEP " "//分隔符
    
    const char* GetUserName()//获取用户名字
    {
        const char* userName = getenv("USER");
        if(userName) 
            return userName;
        else
            return "none";
    }
    
    const char* GetHostName()//获取主机名字
    {
        const char* hostName = getenv("HOSTNAME");
        if(hostName)
            return hostName;
        else
            return "none";
    }
    
    const char* GetPwd()//获取路径地址
    {
        const char* pwd = getenv("PWD");
        if(pwd)
            return pwd;
        else
            return "none";
    }
    
    int main()
    {
    	while(1)
        {
            //1.打印提示信息
            printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
            fflush(stdout);
        
            //2.获取 user 的输入
            memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
            if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
            {
                //出错就直接跳出循环
                continue;
            }
            cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
    
            //3.命令行字符解析
            //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
            g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符
    
            int index = 1;
            if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
            {
                g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
            }
            while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
    
            //4.fork() 创建子进程,execvp() 替换子进程
            pid_t id = fork();
            if(id == 0)//child
            {
                printf("子进程 run:\n");
                execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
                exit(1);//没替换成功就会异常退出
            }
            //father
            int status;
            pid_t ret = waitpid(id, &status, 0);//阻塞等待
            if(ret > 0) 
                printf("exit code: %d\n", WEXITSTATUS(status));
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    3.2.封装步骤

    既然上面的用户名、主机名、路径都进行了函数封装,那么我也可以选择将部分的代码进行分钟,变得更有逻辑。

    #include 
    #include 
    #include 
    #include 
    #include  
    #include 
    #include 
    #include 
    
    #define NUM 1024
    char cmd_line[NUM];//保存完整命令字符串
    
    #define SIZE 32
    char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组
    
    #define SEP " "//分隔符
    
    const char* GetUserName(void)//获取用户名字
    {
        const char* userName = getenv("USER");
        if(userName) 
            return userName;
        else
            return "none";
    }
    
    const char* GetHostName(void)//获取主机名字
    {
        const char* hostName = getenv("HOSTNAME");
        if(hostName)
            return hostName;
        else
            return "none";
    }
    
    const char* GetPwd(void)//获取路径地址
    {
        const char* pwd = getenv("PWD");
        if(pwd)
            return pwd;
        else
            return "none";
    }
    
    void Print(void)
    {
        printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
        fflush(stdout);
    }
    
    bool GetUserCommand()
    {
        memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
        if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
        {
            //出错就直接跳出循环
            return false;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
        return true;
    }
    
    void AnalyzeStr(void)
    {
            //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
            g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符
    
            int index = 1;
    
            //处理特殊的命令情况
            if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
            {
                g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
            }
    
            while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
    }
    
    void RunCommand(void)
    {
        pid_t id = fork();
        if(id == 0)//child
        {
            printf("子进程 run:\n");
            execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
            exit(1);//没替换成功就会异常退出
        }
        else
        {
            //father
            int status;
            pid_t ret = waitpid(id, &status, 0);//阻塞等待
            if(ret > 0) 
            printf("exit code: %d\n", WEXITSTATUS(status));
        }
    }
    
    int main()
    {
    	while(1)
        {
            //1.打印提示信息
            Print();
        
            //2.获取 user 的输入
            if(!GetUserCommand())
                continue;
    
            //3.命令行字符解析
            AnalyzeStr();
    
            //4.fork() 创建子进程,execvp() 替换子进程
            RunCommand();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    3.3.debug

    如果使用了上面的shell程序,很快就会发现两个bug

    3.3.1.无法直接输入换行

    在系统默认运行的shell中,允许用户不断回车,而我们的程序如果直接回车就会退出,这个问题的解决思路很简单,只需要在获取字符的时候,查看用户输入的字符长度是否为0即可,若是就让GetUserCommand()返回false,执行continue语句即可。

    #include 
    #include 
    #include 
    #include 
    #include  
    #include 
    #include 
    #include 
    
    #define NUM 1024
    char cmd_line[NUM];//保存完整命令字符串
    
    #define SIZE 32
    char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组
    
    #define SEP " "//分隔符
    
    const char* GetUserName(void)//获取用户名字
    {
        const char* userName = getenv("USER");
        if(userName) 
            return userName;
        else
            return "none";
    }
    
    const char* GetHostName(void)//获取主机名字
    {
        const char* hostName = getenv("HOSTNAME");
        if(hostName)
            return hostName;
        else
            return "none";
    }
    
    const char* GetPwd(void)//获取路径地址
    {
        const char* pwd = getenv("PWD");
        if(pwd)
            return pwd;
        else
            return "none";
    }
    
    void Print(void)
    {
        printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
        fflush(stdout);
    }
    
    bool GetUserCommand()
    {
        memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
        if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
        {
            //出错就直接跳出循环
            return false;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
        if(strlen(cmd_line) == 0)
            return false;
        return true;
    }
    
    void AnalyzeStr(void)
    {
            //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
            g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符
    
            int index = 1;
    
            //处理特殊的命令情况
            if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
            {
                g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
            }
    
            while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
    }
    void RunCommand(void)
    {
        pid_t id = fork();
        if(id == 0)//child
        {
            printf("子进程 run:\n");
            execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
            exit(1);//没替换成功就会异常退出
        }
        else
        {
            //father
            int status;
            pid_t ret = waitpid(id, &status, 0);//阻塞等待
            if(ret > 0) 
            printf("exit code: %d\n", WEXITSTATUS(status));
        }
    }
    
    int main()
    {
    	while(1)
        {
            //1.打印提示信息
            Print();
        
            //2.获取 user 的输入
            if(!GetUserCommand())
                continue;
    
            //3.命令行字符解析
            AnalyzeStr();
    
            //4.fork() 创建子进程,execvp() 替换子进程
            RunCommand();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    3.3.2.无法切换对应目录

    还可以发现一个比较严重的问题,我们无法使用cd命令切换目录,这是为什么呢?原因也很简单,cd程序替换的是子进程,该目录只让子进程进行了切换命令,而我们希望的是父进程自己执行cd,修改父进程的工作路径。因此,我们需要调用一些库函数,更改父进程的工作环境。

    #include 
    #include 
    #include 
    #include 
    #include  
    #include 
    #include 
    #include 
    
    #define NUM 1024
    char cmd_line[NUM];//保存完整命令字符串
    
    #define SIZE 32
    char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组
    
    #define SEP " "//分隔符
    
    const char* GetUserName(void)//获取用户名字
    {
        const char* userName = getenv("USER");
        if(userName) 
            return userName;
        else
            return "none";
    }
    
    const char* GetHostName(void)//获取主机名字
    {
        const char* hostName = getenv("HOSTNAME");
        if(hostName)
            return hostName;
        else
            return "none";
    }
    
    const char* GetPwd(void)//获取路径地址
    {
        const char* pwd = getenv("PWD");
        if(pwd)
            return pwd;
        else
            return "none";
    }
    
    void Print(void)//打印提示字符
    {
        printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
        fflush(stdout);
    }
    
    bool GetUserCommand(void)//获取用户命令字符串
    {
        memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
        if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
        {
            //出错就直接跳出循环
            return false;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
        if(strlen(cmd_line) == 0)
            return false;
        return true;
    }
    
    void AnalyzeStr(void)//拆分解析用户字符串
    {
        //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
        g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符
    
        int index = 1;
    
        //处理特殊的命令情况
        if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
        {
            g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
        }
    
        while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
    }
    
    bool DoBuildin(void)//判断是否内建命令并且执行
    {
        //内建命令
        if(strcmp(g_argv[0], "cd") == 0)       
        {   
            printf("父进程 run:\n");
            //更改路径的命令
            char* path = NULL;
            if(g_argv[1] == NULL)
            {
                path = ".";
            }
            else
            {
                path = g_argv[1];
            }
            chdir(path);
            return true;
        }
        return false;
    }
    
    void RunCommand(void)//运行用户命令
    {
        pid_t id = fork();
        if(id == 0)//child
        {
            printf("子进程 run:\n");
            execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
            exit(1);//没替换成功就会异常退出
        }
        else
        {
            //father
            int status;
            pid_t ret = waitpid(id, &status, 0);//阻塞等待
            if(ret > 0) 
            printf("exit code: %d\n", WEXITSTATUS(status));
        }
    }
    
    int main()
    {
    	while(1)
        {
            //1.打印提示信息
            Print();
        
            //2.获取 user 的输入
            if(!GetUserCommand())
                continue;
    
            //3.命令行字符解析
            AnalyzeStr();
    
            //4.处理内建命令
            if(DoBuildin())
                continue;
    
            //5.fork() 创建子进程,execvp() 替换子进程
            RunCommand();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144

    这种只能由父进程来执行的命令也叫做“内建命令”,这类环境变量有很多。

    3.3.3.无法更改环境变量

    还有一个比较尴尬的问题,我们发现使用cd指令后,提示字符串的地址无法及时更新,也就是没有更新对应的环境变量。如果更新呢?可以使用系统提供的接口getcwd()来获取调用该接口进程所在的路径。

    #include 
    #include 
    #include 
    #include 
    #include  
    #include 
    #include 
    #include 
    
    #define NUM 1024
    char cmd_line[NUM];//保存完整命令字符串
    
    #define SIZE 32
    char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组
    
    #define SEP " "//分隔符
    
    char cwd[1024];//环境变量
    
    const char* GetUserName(void)//获取用户名字
    {
        const char* userName = getenv("USER");
        if(userName) 
            return userName;
        else
            return "none";
    }
    
    const char* GetHostName(void)//获取主机名字
    {
        const char* hostName = getenv("HOSTNAME");
        if(hostName)
            return hostName;
        else
            return "none";
    }
    
    const char* GetPwd(void)//获取路径地址
    {
        const char* pwd = getenv("PWD");
        if(pwd)
            return pwd;
        else
            return "none";
    }
    
    void Print(void)
    {
        printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
        fflush(stdout);
    }
    
    bool GetUserCommand()
    {
        memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
        if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
        {
            //出错就直接跳出循环
            return false;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
        if(strlen(cmd_line) == 0)
            return false;
        return true;
    }
    
    void AnalyzeStr(void)
    {
        //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
        g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符
    
        int index = 1;
    
        //处理特殊的命令情况
        if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
        {
            g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
        }
    
        while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
    }
    
    bool DoBuildin()
    {
        //内建命令
        if(strcmp(g_argv[0], "cd") == 0)       
        {   
            printf("父进程 run:\n");
            //更改路径的命令
            char* path = NULL;
            if(g_argv[1] == NULL)
            {
                path = ".";
            }
            else
            {
                path = g_argv[1];
            }
            chdir(path);
            
            char tmp[1024];
            getcwd(tmp, sizeof(tmp));//获取当前进程所在的地址(工作目录)
            sprintf(cwd, "PWD=%s", tmp);//组合环境变量
            putenv(cwd);//设置环境变量
            return true;
        }
        return false;
    }
    
    void RunCommand(void)
    {
        pid_t id = fork();
        if(id == 0)//child
        {
            printf("子进程 run:\n");
            execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
            exit(1);//没替换成功就会异常退出
        }
        else
        {
            //father
            int status;
            pid_t ret = waitpid(id, &status, 0);//阻塞等待
            if(ret > 0) 
            printf("exit code: %d\n", WEXITSTATUS(status));
        }
    }
    
    int main()
    {
    	while(1)
        {
            //1.打印提示信息
            Print();
        
            //2.获取 user 的输入
            if(!GetUserCommand())
                continue;
    
            //3.命令行字符解析
            AnalyzeStr();
    
            //4.处理内建命令
            if(DoBuildin())
                continue;
    
            //5.fork() 创建子进程,execvp() 替换子进程
            RunCommand();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
  • 相关阅读:
    Triton推理服务器吞吐量测试
    东方通部署vue项目
    SpringBoot2.x整合Jedis客户端详细过程
    MySql的索引学习和使用;(本人觉得足够详细)
    [论文工具] LaTeX论文撰写常见用法及实战技巧归纳(持续更新)
    基于Vue和Spring Boot的在线点餐系统设计与实现
    json解析服务器List数据问题
    深度学习远程炼丹:一文离线完成ubuntu+docker+pycharm环境配置
    Dapr v1.11 版本已发布
    详解:驱动程序无法通过使用安全套接字层(SSL)加密与SQL Server 建立安全连接。
  • 原文地址:https://blog.csdn.net/m0_73168361/article/details/134529814