前言:做一个 "会创建,会终止,会等待,会程序替换" 的简易 shell 。
1、显示提示符和获取用户输入
shell 本质就是个死循环,我们不关心获取这些属性的接口,如果要实现 shell:
- 1:显示提示符 → #
- 2:获取用户输入 → fgets
- 3:将接收到的字符串拆开 → 把 "ls -a -l" 转换成 "ls" "-a" "-l"
- ……
我们先从简单的入手,先来实现前两步,显示提示符 和 获取用户输入:
1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 #include <unistd.h>5 #include <sys/wait.h>6 #include <sys/types.h>7 8 #define NUM 10249 10 char cmd_line[NUM]; // 用来接收命令行内容11 12 int main(void)13 {14 //0.命令行解释器,一定是一个常驻内存的进程,不退出15 while (1) {16 /* 1:显示提示信息 */17 printf("[amx@hecs-0-1 myshell] # ");18 fflush(stdout);19 20 /* 2:获取用户输入,输入的是各种指令和选项 :"ls -a -l "*/21 memset (22 cmd_line,23 '\0',24 sizeof(cmd_line)25 );26 if( fgets(cmd_line, NUM, stdin)==NULL) {27 continue;28 }/* 从键盘获取,标准输入,stdin 29 获取到 C 风格的字符串,默认添加 '\0' */30 printf("%s\n", cmd_line); 31 }32 }
我们利用 fgets 函数从键盘上获取,标准输入 stdin,获取到 C 风格的字符串,
注意默认会添加 \0 ,我们先把获取到的结果 cmd_line 打印出来看看:
因为 cmd_line 里有一个 \n——我们输入完指令后会按下回车,printf里面又有一个回车 。我们把它替换成 \0 即可:
cmd_line[strlen(cmd_line) - 1] = '\0'; //-1的目的是,最后一个字符的下标是长度减一,strlen不包括\n,\0这些
2、将接收到的字符串拆开
下面我们需要 将接收到的字符串拆开,比如:把 "ls -a -l" 拆成 "ls" "-a" "-l"
因为 exec 函数簇无论是列表传参还是数组传参,一定是要逐个传递的!
我们自己实现的话,就是把这些空格变成\0,让他们输出。
但是我们可以使用 strtok 函数,将一个字符串按照特定的分隔符打散,将子串依次返回:
char* strtok(char* str, const char* delim);
代码:
1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 #include <unistd.h>5 #include <sys/wait.h>6 #include <sys/types.h>7 8 #define NUM 10249 #define SIZE 3210 #define SEP " "//设置分隔符 空格11 12 //保存打散之后的命令行字符串13 char *g_argv[SIZE];14 char cmd_line[NUM]; // 用来接收完整的命令行内容15 16 int main(void)17 {18 //0.命令行解释器,一定是一个常驻内存的进程,不退出19 while (1) {20 /* 1:显示提示信息 */21 printf("[amx@hecs-0-1 myshell] # ");22 fflush(stdout);23 24 /* 2:获取用户输入,输入的是各种指令和选项 :"ls -a -l "*/ 25 memset ( 26 cmd_line, 27 '\0', 28 sizeof(cmd_line) 29 ); 30 if( fgets(cmd_line, NUM, stdin)==NULL) { 31 continue; 32 }/* 从键盘获取,标准输入,stdin 33 获取到 C 风格的字符串,默认添加 '\0' */ 34 cmd_line[strlen(cmd_line) - 1] = '\0'; //-1的目的是,最后一个字符的下标是长度减一,strlen不包括\n,\0这些 35 // printf("%s\n", cmd_line); 36 //3.命令行字符串解析:"ls -a -l "->"ls" "-a" "-l" 37 g_argv[0]= strtok(cmd_line, SEP);//第一次调用,要传入原始字符串 38 int index=1;39 while(g_argv[index++]=strtok(NULL,SEP));//第二次调用,如果还要解析原始字符串,传NULL40 //for debug 41 for(index=0;g_argv[index];index++){ 42 printf("g_argv[%d]:%s\n",index,g_argv[index]); 43 } 44 } 45 }
~
运行结果:
3、创建进程 & 程序替换
下面我们实现 创建进程,执行它。
1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 #include <unistd.h>5 #include <sys/wait.h>6 #include <sys/types.h>7 8 #define NUM 10249 #define SIZE 3210 #define SEP " "//设置分隔符 空格11 12 //保存打散之后的命令行字符串13 char *g_argv[SIZE];14 char cmd_line[NUM]; // 用来接收完整的命令行内容15 16 int main(void)17 {18 //0.命令行解释器,一定是一个常驻内存的进程,不退出19 while (1) {20 /* 1:显示提示信息 */21 printf("[amx@hecs-0-1 myshell] # ");22 fflush(stdout);23 24 /* 2:获取用户输入,输入的是各种指令和选项 :"ls -a -l "*/25 memset (26 cmd_line, 27 '\0', 28 sizeof(cmd_line) 29 ); 30 if( fgets(cmd_line, NUM, stdin)==NULL) {31 continue; 32 }/* 从键盘获取,标准输入,stdin 33 获取到 C 风格的字符串,默认添加 '\0' */34 cmd_line[strlen(cmd_line) - 1] = '\0'; //-1的目的是,最后一个字符的下标是长度减一,strlen不包括\n,\0这些35 // printf("%s\n", cmd_line);36 //3.命令行字符串解析:"ls -a -l "->"ls" "-a" "-l"37 g_argv[0]= strtok(cmd_line, SEP);//第一次调用,要传入原始字符串38 int index=1;
W> 39 while(g_argv[index++]=strtok(NULL,SEP)); 40 //for debug41 // for(index=0;g_argv[index];index++){42 // printf("g_argv[%d]:%s\n",index,g_argv[index]);43 // }44 // 4.TODO45 // 5.fork()46 pid_t id=fork();47 if(id==0){//子进程48 printf("下面功能让子进程执行的\n");49 execvp(g_argv[0],g_argv);//ls -a -l -i50 exit(1);51 }52 else{//父进程53 int status=0; 54 pid_t ret=waitpid(id,&status,0); 55 if(ret>0) printf("exit code: %d\n",WEXITSTATUS(status));56 57 }58 59 }60 }
完美运行,你还可以输入vim进行编写,基本命令都可以!!!!!!!
但是但是但是:
当我推出到上一级时,他的目录并没有发生什么变化,为什么?
当你在cd的时候,无论什么命令都会执行exec,而执行exec命令只会影响当前的子进程的路径变化,而不是父进程路径变化,运行完子进程,子进程就退出了,所以我们还需要再做一些工作------如果是像cd这样的命令,我们不能创建子进程。
像这样,让父亲在自己去执行的命令我们叫做内置命令\内建命令-----本质就是shell中的函数调用。
4、内建命令:实现路径切换
代码实现:
运行结果:
在上层你看到的是个命令,但是在 shell 内部本质上是由父 shell 自己实现、调用的一个函数(并没有创建子进程),这种就是对应上上层的 内建命令。
内建命令表现是用用户层面的一条命令,本质就是 Shell 内部的一个函数,由父 Shell 自己执行,而不创建子进程。
还可以加颜色:
我们which一下可以发现颜色配色。
运行结果:
全部搞定!!!!!!!!
//除了 ll这个命令
我们看,因为ll本身就是ls的别名,如果想让它成立,我们还需要做一些判断:
至此,圆满结束!!!