【Linux】进程篇(补):简易 shell 的实现(进程深刻理解、内建命令的使用)

  • Post author:
  • Post category:linux




代码逻辑框架

(边写边查)

  1. 命令行提示符,fflush 刷新显示
  2. 获取 输入的 有效字符串,定义一个字符数组,用 fgets 从键盘上获取(注意处理命令行输入时的回车)
  3. 字符串切割,C语言函数 strtok,子串保存到指针数组
  4. 创建子进程,执行代码


增加:

  1. ls 的配色方案

  2. 内建命令的处理


  • cd ..


    cd /

    这样的命令,需要让 bash 自己执行的,叫做 内建命令 / 内置命令

  • export

    ,一般用户自定义的环境变量,在 bash 中要用户自己来进行维护,不要用一个经常被覆盖的缓冲区来保存环境变量!!!

  • env

    ,我们一般需要 env 时,都要查看的是自己进程的环境列表,这里就是查看 bash 的环境列表。

  • echo

    ,利用指针查找和输出

  • echo $?

    ,取出子进程退出码

  1. >

    重定向符,实现方法见注释

其实我们之前提到过的几乎所有环境变量命令,都是 内建命令。




makefile


mybash:mybash.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f mybash



mybash.c


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MAX 1024
#define ARGC 64
#define SEP " "

enum redir{
	REDIR_INPUT = 0,
	REDIR_OUTPUT,
	REDIR_APPEND,
	REDIR_NONE
};

char * checkDir(char commandstr[], redir &redir_type)
{
	// 1. 检测 commandstr 内部是否有 > < >>
	// 2. 如果有,根据 > < >>,设置 redir_type = X
	// 3. 将 > >> < -> '\0',即将 commandstr 设置成两部分
	// 4. 保存文件名,并返回
	// 5. 如果没有重定向,直接放回
	return NULL;
}


int split(char *commandstr, char *argv[])
{
    assert(commandstr);
    assert(argv);

    argv[0] = strtok(commandstr, SEP);
    if(argv[0] == NULL) return -1;
    int i = 1;
    while((argv[i++] = strtok(NULL, SEP)));
    //int i = 1;
    //while(1)
    //{
    //    argv[i] = strtok(NULL, SEP);
    //    if(argv[i] == NULL) break;
    //    i++;
    //}
    return 0;
}

void debugPrint(char *argv[])
{
    for(int i = 0; argv[i]; i++)
    {
        printf("%d: %s\n", i, argv[i]);
    }
}

void showEnv()
{
    extern char **environ;
    for(int i = 0; environ[i]; i++) printf("%d:%s\n", i, environ[i]);
}

int main()
{
    //当我们在进行 env 查看的时候,我们想查的是谁的环境变量列表?父进程 bash 的环境变量列表
    int last_exit = 0;
    char myenv[32][256];
    int env_index = 0;

    while(1)
    {
        char commandstr[MAX] = {0};		// 命令行输入的,完整字符串
        char *argv[ARGC] = {NULL};		// 命令行输入的,子串
        redir redir_type = REDIR_NONE;
        // 1
        printf("[Stella@hostname currpath]# ");
        fflush(stdout);		// 把数据从缓冲区刷新显示出来
        // cat Makefile > log.txt
        // cat Makefile >> log.txt
        // cat Makefile < log.txt  
                
        // 2
        char *s = fgets(commandstr, sizeof(commandstr), stdin);
        assert(s);
        (void)s; // 保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用
        // abcd\n\0
        commandstr[strlen(commandstr)-1] = '\0';

		// (3)【重定向】
		char *filename = checkDir(commandstr, &redir_type);

        
        // 3
        // "ls -a -l" -> "ls" "-a" "-l"
        int n = split(commandstr, argv);
        if(n != 0) continue;
        //debugPrint(argv);
    
        // (1)【添加 ls 命令的配色方案】
        if(strcmp(argv[0], "ls") == 0)	
        {
            int pos = 0;
            while(argv[pos]) pos++;
            argv[pos++] = (char*)"--color=auto";加配色方案
            argv[pos] = NULL; // 比较安全的做法
        }
        // (2)【内建命令】
        // cd .. / cd /: 让bash自己执行的命令,我们称之为 内建命令/内置命令
        if(strcmp(argv[0], "cd") == 0)
        {
            //说到底,cd 命令,重要的表现就如同 bash 自己调用了对应的函数
            if(argv[1] != NULL) chdir(argv[1]);	// chdir是一个系统调用函数,可以改变当前工作路径
            continue;
        }
        else if(strcmp(argv[0], "export") == 0) // 其实我们之前学习到的所有的(几乎)环境变量命令,都是内建命令
        {
            if(argv[1] != NULL){
                strcpy(myenv[env_index], argv[1]);
                putenv(myenv[env_index++]);
            }
            continue;
        }
        else if(strcmp(argv[0], "env") == 0)
        {
            showEnv();
            continue;
        }
        else if(strcmp(argv[0], "echo") == 0)
        {
            // echo $PATH
            const char *target_env = NULL;
            if(argv[1][0] == '$') 
            {
                if(argv[1][1] == '?'){
                    printf("%d\n", last_exit);
                    continue;
                }
                else target_env = getenv(argv[1]+1); // "abcdefg
                
                if(target_env != NULL) printf("%s=%s\n", argv[1]+1, target_env);
            }
            continue;
        }

        
		// 4
		// version 1
        pid_t id = fork();
        assert(id >= 0);
        (void)id;

        if(id == 0)
        {
            if(redir_type != REDIR_NONE)
            {
               // 1. 存在文件的
               // 2. redir_type获取到
               // 3. dup2;
            }
            //child
            execvp(argv[0], argv);
            exit(1);
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret > 0){
            last_exit = WEXITSTATUS(status);
        }
        //printf("%s\n", commandstr);
    }
}



版权声明:本文为m0_67470729原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。