当前位置: 首页 > news >正文

Linux【编写一个简单的shell】

目录

0.基本思路

1.打印出提示信息

2.获取用户的输入

3.将字符串拆分

5.TODO

4.TODO

6.给我们的命令添加颜色

7.支持别名

8.总结


shell运行原理:通过让子进程执行命令,父进程等待,解析命令,即可完成对应的命令行解释器。

(子进程如果执行命令出错了,也不会影响父进程)


0.基本思路

命令行解释器一定是一个常驻内存的进程(不退出的程序)!

所以我们上来就执行while(1),也就是创建死循环


1.打印出提示信息

这里我们减少学习成本,直接粘贴和打印出来(其实这个打印出来的信息都是可以调用指定的接口获取到的)
当然我们也可以做一点小小的改动,就比方说下面这样

[root@localhost myshell]#
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//系统相关的头文件一般放在最后
#include <sys/wait.h>
int main()
{
    while(1)
    {
        //1.打印出提示信息
        //这里我们减少学习成本,直接粘贴和打印出来
        printf("[root@localhost myshell]#");
        //将提示符信息立马刷新出来,不然其信息一直都会呆在缓冲区内,不被打印出来
        fflush(stdout);
        sleep(10);
    }
}

2.获取用户的输入

输入的是各种指令和选项:比方说"ls -a -l -i"

使用fgets,从特定的文件流中获取文件,大小是size,获取成功就是返回缓冲区的起始地址,获取失败就是NULL 

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//系统相关的头文件一般放在最后
#include <sys/wait.h>
//定义一个大小为1024个字节的缓冲区
#define NUM 1024
char cmd_line[NUM];

int main()
{
    while(1)
    {
        //1.打印出提示信息
        //这里我们减少学习成本,直接粘贴和打印出来
        printf("[root@localhost myshell]#");
        //将提示符信息立马刷新出来,不然其信息一直都会呆在缓冲区内,不被打印出来
        fflush(stdout);
        //sizeof可以不带圆括号,直接求大小
        //初始化我们的缓冲区,将我们的缓冲区也就是cmd_line中的全部内容(也就是大小为sizeof(cmd_line)的空间)全部初始化为'\0'
        memset(cmd_line,'\0',sizeof cmd_line);
        //2.获取用户的键盘输入
        //第一个参数是将我们读取到的字符放入哦我们上面定义的缓冲区中,第二个参数是定义放入的字符的大小,第三个参数是指从输入流中读取
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
        {
            //如果读取到的是空,也就是出错了,就直接进行下一次循环,重新初始化
            continue;
        }
        //将我们输入的内容打印出来
        //但是我们输入的内容实际上最后带有一个\n
        //ls -a -l -i\n
        //我们需要将这个最后的\n给去除掉
        //strlen求字符串长度的时候不包括后面的\0
        //由于我们的下标是从0开始的,所以我们需要将cmd_line的长度-1的位置,也就是\n的位置置为\0
        cmd_line[strlen(cmd_line)-1]='\0';
        printf("echo:%s\n",cmd_line);
    }
}

 对于我们上面strlen(cmd_line)-1='\0'的小小的解释

#include <stdio.h>
#include <string>
int main()
{
    char a[]="12345\n";
    printf("%d\n",strlen(a));
    a[sizeof a-1]='\0';
    printf("%s\n",a);
}


3.将字符串拆分

"ls -a -l -i"转换成"ls","-a","-l","-i"的子字符串,我们需要对命令行字符串做解析工作

我们将字符串中的空格部分置为\0并且将每个子字符串的起始地址用指针指向,就得到了我们的打散的子字符串

把一个字符串打散成为多个子串,在c语言中有这样的接口吗? 

第一个参数是你要打散的字符串,第二个参数是你要设置的分隔符,而且只会返回有效的子串

char *strtok(char *str, const char *delim);
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//系统相关的头文件一般放在最后
#include <sys/wait.h>
//定义一个大小为1024个字节的缓冲区
#define NUM 1024
//第一一个用于打散之后的字符串的个数的大小
#define SIZE 32
//宏定义分隔符
//由于我们下面的分隔符系统中在定义的时候是一个char*的,所以我们必须传入一个字符串,所以我们下面用的SEP必须要是双引号包裹起来的空格
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串,也就是将每一个打散之后的子串的起始地址保存在这个g_argv中
char *g_argv[SIZE];


int main()
{
    while(1)
    {
        //1.打印出提示信息
        //这里我们减少学习成本,直接粘贴和打印出来
        printf("[root@localhost myshell]#");
        //将提示符信息立马刷新出来,不然其信息一直都会呆在缓冲区内,不被打印出来
        fflush(stdout);
        //sizeof可以不带圆括号,直接求大小
        //初始化我们的缓冲区,将我们的缓冲区也就是cmd_line中的全部内容(也就是大小为sizeof(cmd_line)的空间)全部初始化为'\0'
        memset(cmd_line,'\0',sizeof cmd_line);
        //2.获取用户的键盘输入
        //第一个参数是将我们读取到的字符放入哦我们上面定义的缓冲区中,第二个参数是定义放入的字符的大小,第三个参数是指从输入流中读取
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
        {
            //如果读取到的是空,也就是出错了,就直接进行下一次循环,重新初始化
            continue;
        }
        //将我们输入的内容打印出来
        //但是我们输入的内容实际上最后带有一个\n
        //ls -a -l -i\n
        //我们需要将这个最后的\n给去除掉
        //strlen求字符串长度的时候不包括后面的\0
        //由于我们的下标是从0开始的,所以我们需要将cmd_line的长度-1的位置,也就是\n的位置置为\0
        cmd_line[strlen(cmd_line)-1]='\0';
//        printf("echo:%s\n",cmd_line);
        //3.打散字符串
        //我们将字符串中的空格部分置为\0并且将每个子字符串的起始地址用指针指向,就得到了我们的打散的子字符串
        //第一个参数你要解析的子串,第二个参数是分隔符
        g_argv[0]=strtok(cmd_line,SEP);//第一次调用,要传入原始字符串
        int index=1;
        //进行循环读取
//        while(g_argv[index-1])
//        {
//            g_argv[index]= strtok(NULL,SEP);//第二次如果还要解析原始字符串,传入NULL,也就是第一个参数,第二个参数分隔符还是SEP
//            index++;
//        }
        //先解析,再赋值,最后while解析g_argv,解析到最后,全部都解析完成的时候g_argv的值为null,while循环条件不满足,循环退出
        while(g_argv[index++]= strtok(NULL,SEP));

        //我们测试一下打散的操作成不成功
        for(index=0;g_argv[index];index++)
        {
            printf("g_argv[%d]:%s\n",index,g_argv[index]);
        }
    }
}

 


5.创建子进程

第四步放到稍后再写。我们的第五步先进行创建子进程

子进程使用程序替换的接口excel来调用对应的程序

我们这里选择execvp因为这样我们就直接可以把我们的参数传进去,也就是我们打散之后的数据g_argv[],并且execvp还会自动从系统的路径中查找对应的程序 

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//系统相关的头文件一般放在最后
#include <sys/wait.h>
#include <sys/types.h>
//定义一个大小为1024个字节的缓冲区
#define NUM 1024
//第一一个用于打散之后的字符串的个数的大小
#define SIZE 32
//宏定义分隔符
//由于我们下面的分隔符系统中在定义的时候是一个char*的,所以我们必须传入一个字符串,所以我们下面用的SEP必须要是双引号包裹起来的空格
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串,也就是将每一个打散之后的子串的起始地址保存在这个g_argv中
char *g_argv[SIZE];


int main()
{
    while(1)
    {
        //1.打印出提示信息
        //这里我们减少学习成本,直接粘贴和打印出来
        printf("[root@localhost myshell]#");
        //将提示符信息立马刷新出来,不然其信息一直都会呆在缓冲区内,不被打印出来
        fflush(stdout);
        //sizeof可以不带圆括号,直接求大小
        //初始化我们的缓冲区,将我们的缓冲区也就是cmd_line中的全部内容(也就是大小为sizeof(cmd_line)的空间)全部初始化为'\0'
        memset(cmd_line,'\0',sizeof cmd_line);
        //2.获取用户的键盘输入
        //第一个参数是将我们读取到的字符放入哦我们上面定义的缓冲区中,第二个参数是定义放入的字符的大小,第三个参数是指从输入流中读取
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
        {
            //如果读取到的是空,也就是出错了,就直接进行下一次循环,重新初始化
            continue;
        }
        //将我们输入的内容打印出来
        //但是我们输入的内容实际上最后带有一个\n
        //ls -a -l -i\n
        //我们需要将这个最后的\n给去除掉
        //strlen求字符串长度的时候不包括后面的\0
        //由于我们的下标是从0开始的,所以我们需要将cmd_line的长度-1的位置,也就是\n的位置置为\0
        cmd_line[strlen(cmd_line)-1]='\0';
//        printf("echo:%s\n",cmd_line);
        //3.打散字符串
        //我们将字符串中的空格部分置为\0并且将每个子字符串的起始地址用指针指向,就得到了我们的打散的子字符串
        //第一个参数你要解析的子串,第二个参数是分隔符
        g_argv[0]=strtok(cmd_line,SEP);//第一次调用,要传入原始字符串
        int index=1;
        //进行循环读取
//        while(g_argv[index-1])
//        {
//            g_argv[index]= strtok(NULL,SEP);//第二次如果还要解析原始字符串,传入NULL,也就是第一个参数,第二个参数分隔符还是SEP
//            index++;
//        }
        //先解析,再赋值,最后while解析g_argv,解析到最后,全部都解析完成的时候g_argv的值为null,while循环条件不满足,循环退出
        while(g_argv[index++]= strtok(NULL,SEP));

        //我们测试一下打散的操作成不成功
//        for(index=0;g_argv[index];index++)
//        {
//            printf("g_argv[%d]:%s\n",index,g_argv[index]);
//        }

        //4.TODO
        //5.fork()
        pid_t id=fork();
        if(id==0)//child
        {
            printf("下面功能让子进程执行的\n");
            //ls -a -l -i,第一个参数g_argv[0]保存的就是我们的命令,后面的全部都是我们的参数
            execvp(g_argv[0],g_argv);
            exit(1);
        }
        //father
        int status=0;
        //阻塞式等待
        pid_t ret= waitpid(id,& status,0);
        if(ret>0)
        {
            printf("exit code:%d\n", WEXITSTATUS(status));
        }
    }
}

下面我们如果想要在我们的shell中删除内容的话,需要ctrl+删除 (mac的话是对应的control+delete)  

我们再进行进一步的测试

nano mytest.c

写入下面的代码 

#include <stdio.h>
int main()
{
    printf("hello world\n");
    return 0;
}

直接ctrl+x退出

我们看到我们自己写的shell也可以运行我们在shell中编写的程序了

 


4.TODO

内置命令

我们观察到如果是使用系统默认的shell的话,我们在路径发生改变的时候,前面的提示信息也是会发生改变的

 但是如果我们是用自己编写的shell执行上面的操作的话,路径是不会发生变化的。

按道理来讲,我们的路径应该回退。

但是因为这是我们自己的shell,无论是什么命令,都会去执行execl,所以无论是什么命令,都仅仅是去影响我们的子进程,并不会影响我们的父进程。但是我们想让我们的 shell的路径发生变化。

所以我们这里需要做一个工作,就是判断传入的命令,比方说cd这样的命令,不能创建子进程,而是交给父进程去执行。

内置命令:让父进程shell自己执行的命令,我们叫做内置命令,内建命令

内建命令本质上就是shell内部中的一个函数

 这里我们调用chdir命令来改变我们的工作区

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//系统相关的头文件一般放在最后
#include <sys/wait.h>
#include <sys/types.h>
//定义一个大小为1024个字节的缓冲区
#define NUM 1024
//第一一个用于打散之后的字符串的个数的大小
#define SIZE 32
//宏定义分隔符
//由于我们下面的分隔符系统中在定义的时候是一个char*的,所以我们必须传入一个字符串,所以我们下面用的SEP必须要是双引号包裹起来的空格
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串,也就是将每一个打散之后的子串的起始地址保存在这个g_argv中
char *g_argv[SIZE];


int main()
{
    while(1)
    {
        //1.打印出提示信息
        //这里我们减少学习成本,直接粘贴和打印出来
        printf("[root@localhost myshell]#");
        //将提示符信息立马刷新出来,不然其信息一直都会呆在缓冲区内,不被打印出来
        fflush(stdout);
        //sizeof可以不带圆括号,直接求大小
        //初始化我们的缓冲区,将我们的缓冲区也就是cmd_line中的全部内容(也就是大小为sizeof(cmd_line)的空间)全部初始化为'\0'
        memset(cmd_line,'\0',sizeof cmd_line);
        //2.获取用户的键盘输入
        //第一个参数是将我们读取到的字符放入哦我们上面定义的缓冲区中,第二个参数是定义放入的字符的大小,第三个参数是指从输入流中读取
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
        {
            //如果读取到的是空,也就是出错了,就直接进行下一次循环,重新初始化
            continue;
        }
        //将我们输入的内容打印出来
        //但是我们输入的内容实际上最后带有一个\n
        //ls -a -l -i\n
        //我们需要将这个最后的\n给去除掉
        //strlen求字符串长度的时候不包括后面的\0
        //由于我们的下标是从0开始的,所以我们需要将cmd_line的长度-1的位置,也就是\n的位置置为\0
        cmd_line[strlen(cmd_line)-1]='\0';
//        printf("echo:%s\n",cmd_line);
        //3.打散字符串
        //我们将字符串中的空格部分置为\0并且将每个子字符串的起始地址用指针指向,就得到了我们的打散的子字符串
        //第一个参数你要解析的子串,第二个参数是分隔符
        g_argv[0]=strtok(cmd_line,SEP);//第一次调用,要传入原始字符串
        int index=1;
        //进行循环读取
//        while(g_argv[index-1])
//        {
//            g_argv[index]= strtok(NULL,SEP);//第二次如果还要解析原始字符串,传入NULL,也就是第一个参数,第二个参数分隔符还是SEP
//            index++;
//        }
        //先解析,再赋值,最后while解析g_argv,解析到最后,全部都解析完成的时候g_argv的值为null,while循环条件不满足,循环退出
        while(g_argv[index++]= strtok(NULL,SEP));

        //我们测试一下打散的操作成不成功
//        for(index=0;g_argv[index];index++)
//        {
//            printf("g_argv[%d]:%s\n",index,g_argv[index]);
//        }

        //4.TODO内置命令
        //内置命令:让父进程shell自己执行的命令,我们叫做内置命令,内建命令
        //内建命令本质上就是shell内部中的一个函数
        if(strcmp(g_argv[0],"cd")==0)//不让我们的子进程去执行cd命令,而是交给我们的父进程去完成
        {
            if(g_argv[1]!=NULL)
            {
                //将要切换的目标路径传进来
                chdir(g_argv[1]);//cd ..

            }
            //进入下一次循环
            continue;
        }

        //5.fork()
        pid_t id=fork();
        if(id==0)//child
        {
            printf("下面功能让子进程执行的\n");
            //ls -a -l -i,第一个参数g_argv[0]保存的就是我们的命令,后面的全部都是我们的参数
            execvp(g_argv[0],g_argv);
            exit(1);
        }
        //father
        int status=0;
        //阻塞式等待
        pid_t ret= waitpid(id,& status,0);
        if(ret>0)
        {
            printf("exit code:%d\n", WEXITSTATUS(status));
        }
    }
}

我们观察到现在我们的shell的路径就会发生变化了 ,现在我们就实现了一个跨路径可执行程序的shell


6.给我们的命令添加颜色

我们看到系统自己的ls是自带选项--color=auto的


#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//系统相关的头文件一般放在最后
#include <sys/wait.h>
#include <sys/types.h>
//定义一个大小为1024个字节的缓冲区
#define NUM 1024
//第一一个用于打散之后的字符串的个数的大小
#define SIZE 32
//宏定义分隔符
//由于我们下面的分隔符系统中在定义的时候是一个char*的,所以我们必须传入一个字符串,所以我们下面用的SEP必须要是双引号包裹起来的空格
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串,也就是将每一个打散之后的子串的起始地址保存在这个g_argv中
char *g_argv[SIZE];


int main()
{
    while(1)
    {
        //1.打印出提示信息
        //这里我们减少学习成本,直接粘贴和打印出来
        printf("[root@localhost myshell]#");
        //将提示符信息立马刷新出来,不然其信息一直都会呆在缓冲区内,不被打印出来
        fflush(stdout);
        //sizeof可以不带圆括号,直接求大小
        //初始化我们的缓冲区,将我们的缓冲区也就是cmd_line中的全部内容(也就是大小为sizeof(cmd_line)的空间)全部初始化为'\0'
        memset(cmd_line,'\0',sizeof cmd_line);
        //2.获取用户的键盘输入
        //第一个参数是将我们读取到的字符放入哦我们上面定义的缓冲区中,第二个参数是定义放入的字符的大小,第三个参数是指从输入流中读取
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
        {
            //如果读取到的是空,也就是出错了,就直接进行下一次循环,重新初始化
            continue;
        }
        //将我们输入的内容打印出来
        //但是我们输入的内容实际上最后带有一个\n
        //ls -a -l -i\n
        //我们需要将这个最后的\n给去除掉
        //strlen求字符串长度的时候不包括后面的\0
        //由于我们的下标是从0开始的,所以我们需要将cmd_line的长度-1的位置,也就是\n的位置置为\0
        cmd_line[strlen(cmd_line)-1]='\0';
//        printf("echo:%s\n",cmd_line);
        //3.打散字符串
        //我们将字符串中的空格部分置为\0并且将每个子字符串的起始地址用指针指向,就得到了我们的打散的子字符串
        //第一个参数你要解析的子串,第二个参数是分隔符
        g_argv[0]=strtok(cmd_line,SEP);//第一次调用,要传入原始字符串
        int index=1;
        //6.给我们的shell设置颜色
        if(strcmp(g_argv[0],"ls")==0)
        {
            g_argv[index++]=(char*)"--color=auto";
        }

        //进行循环读取
//        while(g_argv[index-1])
//        {
//            g_argv[index]= strtok(NULL,SEP);//第二次如果还要解析原始字符串,传入NULL,也就是第一个参数,第二个参数分隔符还是SEP
//            index++;
//        }
        //先解析,再赋值,最后while解析g_argv,解析到最后,全部都解析完成的时候g_argv的值为null,while循环条件不满足,循环退出
        while(g_argv[index++]= strtok(NULL,SEP));

        //我们测试一下打散的操作成不成功
//        for(index=0;g_argv[index];index++)
//        {
//            printf("g_argv[%d]:%s\n",index,g_argv[index]);
//        }

        //4.TODO内置命令
        //内置命令:让父进程shell自己执行的命令,我们叫做内置命令,内建命令
        //内建命令本质上就是shell内部中的一个函数
        if(strcmp(g_argv[0],"cd")==0)//不让我们的子进程去执行cd命令,而是交给我们的父进程去完成
        {
            if(g_argv[1]!=NULL)
            {
                //将要切换的目标路径传进来
                chdir(g_argv[1]);//cd ..

            }
            //进入下一次循环
            continue;
        }

        //5.fork()
        pid_t id=fork();
        if(id==0)//child
        {
            printf("下面功能让子进程执行的\n");
            //ls -a -l -i,第一个参数g_argv[0]保存的就是我们的命令,后面的全部都是我们的参数
            execvp(g_argv[0],g_argv);
            exit(1);
        }
        //father
        int status=0;
        //阻塞式等待
        pid_t ret= waitpid(id,& status,0);
        if(ret>0)
        {
            printf("exit code:%d\n", WEXITSTATUS(status));
        }
    }
}

现在我们自己写的shell就是带颜色的。

 


7.支持别名

ll本质上就是ls的别名,但是我们的现在自己的shell是不支持ll的,也就是不支持别名

这里我们仅仅是让我们的shell支持ls的别名ll


#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//系统相关的头文件一般放在最后
#include <sys/wait.h>
#include <sys/types.h>
//定义一个大小为1024个字节的缓冲区
#define NUM 1024
//第一一个用于打散之后的字符串的个数的大小
#define SIZE 32
//宏定义分隔符
//由于我们下面的分隔符系统中在定义的时候是一个char*的,所以我们必须传入一个字符串,所以我们下面用的SEP必须要是双引号包裹起来的空格
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串,也就是将每一个打散之后的子串的起始地址保存在这个g_argv中
char *g_argv[SIZE];


int main()
{
    while(1)
    {
        //1.打印出提示信息
        //这里我们减少学习成本,直接粘贴和打印出来
        printf("[root@localhost myshell]#");
        //将提示符信息立马刷新出来,不然其信息一直都会呆在缓冲区内,不被打印出来
        fflush(stdout);
        //sizeof可以不带圆括号,直接求大小
        //初始化我们的缓冲区,将我们的缓冲区也就是cmd_line中的全部内容(也就是大小为sizeof(cmd_line)的空间)全部初始化为'\0'
        memset(cmd_line,'\0',sizeof cmd_line);
        //2.获取用户的键盘输入
        //第一个参数是将我们读取到的字符放入哦我们上面定义的缓冲区中,第二个参数是定义放入的字符的大小,第三个参数是指从输入流中读取
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
        {
            //如果读取到的是空,也就是出错了,就直接进行下一次循环,重新初始化
            continue;
        }
        //将我们输入的内容打印出来
        //但是我们输入的内容实际上最后带有一个\n
        //ls -a -l -i\n
        //我们需要将这个最后的\n给去除掉
        //strlen求字符串长度的时候不包括后面的\0
        //由于我们的下标是从0开始的,所以我们需要将cmd_line的长度-1的位置,也就是\n的位置置为\0
        cmd_line[strlen(cmd_line)-1]='\0';
//        printf("echo:%s\n",cmd_line);
        //3.打散字符串
        //我们将字符串中的空格部分置为\0并且将每个子字符串的起始地址用指针指向,就得到了我们的打散的子字符串
        //第一个参数你要解析的子串,第二个参数是分隔符
        g_argv[0]=strtok(cmd_line,SEP);//第一次调用,要传入原始字符串
        int index=1;
        //6.给我们的shell设置颜色
        if(strcmp(g_argv[0],"ls")==0)
        {
            g_argv[index++]=(char*)"--color=auto";
        }
        //7.让我们的shell支持别名
        //这里我们仅仅是支持ll,也就是ls的别名
        if(strcmp(g_argv[0],"ll")==0)
        {
            g_argv[0]=(char*)"ls";
            g_argv[index++]=(char*)"-l";
            g_argv[index++]=(char*)"--color=auto";
        }
        //进行循环读取
//        while(g_argv[index-1])
//        {
//            g_argv[index]= strtok(NULL,SEP);//第二次如果还要解析原始字符串,传入NULL,也就是第一个参数,第二个参数分隔符还是SEP
//            index++;
//        }
        //先解析,再赋值,最后while解析g_argv,解析到最后,全部都解析完成的时候g_argv的值为null,while循环条件不满足,循环退出
        while(g_argv[index++]= strtok(NULL,SEP));

        //我们测试一下打散的操作成不成功
//        for(index=0;g_argv[index];index++)
//        {
//            printf("g_argv[%d]:%s\n",index,g_argv[index]);
//        }

        //4.TODO内置命令
        //内置命令:让父进程shell自己执行的命令,我们叫做内置命令,内建命令
        //内建命令本质上就是shell内部中的一个函数
        if(strcmp(g_argv[0],"cd")==0)//不让我们的子进程去执行cd命令,而是交给我们的父进程去完成
        {
            if(g_argv[1]!=NULL)
            {
                //将要切换的目标路径传进来
                chdir(g_argv[1]);//cd ..

            }
            //进入下一次循环
            continue;
        }

        //5.fork()
        pid_t id=fork();
        if(id==0)//child
        {
            printf("下面功能让子进程执行的\n");
            //ls -a -l -i,第一个参数g_argv[0]保存的就是我们的命令,后面的全部都是我们的参数
            execvp(g_argv[0],g_argv);
            exit(1);
        }
        //father
        int status=0;
        //阻塞式等待
        pid_t ret= waitpid(id,& status,0);
        if(ret>0)
        {
            printf("exit code:%d\n", WEXITSTATUS(status));
        }
    }
}

OK,现在已经成功执行了 

 

8.总结

以下就是最终代码


#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//系统相关的头文件一般放在最后
#include <sys/wait.h>
#include <sys/types.h>
//定义一个大小为1024个字节的缓冲区
#define NUM 1024
//第一一个用于打散之后的字符串的个数的大小
#define SIZE 32
//宏定义分隔符
//由于我们下面的分隔符系统中在定义的时候是一个char*的,所以我们必须传入一个字符串,所以我们下面用的SEP必须要是双引号包裹起来的空格
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串,也就是将每一个打散之后的子串的起始地址保存在这个g_argv中
char *g_argv[SIZE];


int main()
{
    while(1)
    {
        //1.打印出提示信息
        //这里我们减少学习成本,直接粘贴和打印出来
        printf("[root@localhost myshell]#");
        //将提示符信息立马刷新出来,不然其信息一直都会呆在缓冲区内,不被打印出来
        fflush(stdout);
        //sizeof可以不带圆括号,直接求大小
        //初始化我们的缓冲区,将我们的缓冲区也就是cmd_line中的全部内容(也就是大小为sizeof(cmd_line)的空间)全部初始化为'\0'
        memset(cmd_line,'\0',sizeof cmd_line);
        //2.获取用户的键盘输入
        //第一个参数是将我们读取到的字符放入哦我们上面定义的缓冲区中,第二个参数是定义放入的字符的大小,第三个参数是指从输入流中读取
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
        {
            //如果读取到的是空,也就是出错了,就直接进行下一次循环,重新初始化
            continue;
        }
        //将我们输入的内容打印出来
        //但是我们输入的内容实际上最后带有一个\n
        //ls -a -l -i\n
        //我们需要将这个最后的\n给去除掉
        //strlen求字符串长度的时候不包括后面的\0
        //由于我们的下标是从0开始的,所以我们需要将cmd_line的长度-1的位置,也就是\n的位置置为\0
        cmd_line[strlen(cmd_line)-1]='\0';
//        printf("echo:%s\n",cmd_line);
        //3.打散字符串
        //我们将字符串中的空格部分置为\0并且将每个子字符串的起始地址用指针指向,就得到了我们的打散的子字符串
        //第一个参数你要解析的子串,第二个参数是分隔符
        g_argv[0]=strtok(cmd_line,SEP);//第一次调用,要传入原始字符串
        int index=1;
        //6.给我们的shell设置颜色
        if(strcmp(g_argv[0],"ls")==0)
        {
            g_argv[index++]=(char*)"--color=auto";
        }
        //7.让我们的shell支持别名
        //这里我们仅仅是支持ll,也就是ls的别名
        if(strcmp(g_argv[0],"ll")==0)
        {
            g_argv[0]=(char*)"ls";
            g_argv[index++]=(char*)"-l";
            g_argv[index++]=(char*)"--color=auto";
        }
        //进行循环读取
//        while(g_argv[index-1])
//        {
//            g_argv[index]= strtok(NULL,SEP);//第二次如果还要解析原始字符串,传入NULL,也就是第一个参数,第二个参数分隔符还是SEP
//            index++;
//        }
        //先解析,再赋值,最后while解析g_argv,解析到最后,全部都解析完成的时候g_argv的值为null,while循环条件不满足,循环退出
        while(g_argv[index++]= strtok(NULL,SEP));

        //我们测试一下打散的操作成不成功
//        for(index=0;g_argv[index];index++)
//        {
//            printf("g_argv[%d]:%s\n",index,g_argv[index]);
//        }

        //4.TODO内置命令
        //内置命令:让父进程shell自己执行的命令,我们叫做内置命令,内建命令
        //内建命令本质上就是shell内部中的一个函数
        if(strcmp(g_argv[0],"cd")==0)//不让我们的子进程去执行cd命令,而是交给我们的父进程去完成
        {
            if(g_argv[1]!=NULL)
            {
                //将要切换的目标路径传进来
                chdir(g_argv[1]);//cd ..

            }
            //进入下一次循环
            continue;
        }

        //5.fork()
        pid_t id=fork();
        if(id==0)//child
        {
            printf("下面功能让子进程执行的\n");
            //ls -a -l -i,第一个参数g_argv[0]保存的就是我们的命令,后面的全部都是我们的参数
            execvp(g_argv[0],g_argv);
            exit(1);
        }
        //father
        int status=0;
        //阻塞式等待
        pid_t ret= waitpid(id,& status,0);
        if(ret>0)
        {
            printf("exit code:%d\n", WEXITSTATUS(status));
        }
    }
}

相关文章:

  • Python入门到进阶——流程控制
  • 基于Docker的开源端到端开发者平台
  • Chapter2.2:控制系统的数学模型
  • 前端关于cookie那些事儿
  • git——仓库合并不丢失git记录
  • 【云原生】MySQL on k8s 环境部署
  • Jetpack ViewModel源码分析
  • 数字逻辑第二章笔记
  • 从开发角度看羊了个羊
  • 我用PaddleOCR把Halcon论坛的OCR帖子试了一遍,结果。。。
  • 微信号怎么改
  • Spring集成Apache Kafka教程
  • 基于SSM实现图书馆座位预约系统
  • java部分排序算法
  • Java8-特性
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • k8s 面向应用开发者的基础命令
  • PAT A1120
  • webgl (原生)基础入门指南【一】
  • 对象引论
  • 解决iview多表头动态更改列元素发生的错误
  • 聊一聊前端的监控
  • 思考 CSS 架构
  • 最近的计划
  • k8s使用glusterfs实现动态持久化存储
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • 第二十章:异步和文件I/O.(二十三)
  • 机器人开始自主学习,是人类福祉,还是定时炸弹? ...
  • ​HTTP与HTTPS:网络通信的安全卫士
  • ​MySQL主从复制一致性检测
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #pragma 指令
  • #stm32整理(一)flash读写
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (备忘)Java Map 遍历
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (一)认识微服务
  • (转)视频码率,帧率和分辨率的联系与区别
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .net 设置默认首页
  • .NET 药厂业务系统 CPU爆高分析
  • .Net 应用中使用dot trace进行性能诊断
  • .NET 中什么样的类是可使用 await 异步等待的?
  • .NET连接数据库方式
  • .NET序列化 serializable,反序列化
  • [ vulhub漏洞复现篇 ] Apache Flink目录遍历(CVE-2020-17519)
  • [52PJ] Java面向对象笔记(转自52 1510988116)
  • [⑧ADRV902x]: Digital Pre-Distortion (DPD)学习笔记
  • [android学习笔记]学习jni编程