前言:做一个 "会创建,会终止,会等待,会程序替换" 的简易 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 1024
9
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 1024
9 #define SIZE 32
10 #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));//第二次调用,如果还要解析原始字符串,传NULL
40 //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 1024
9 #define SIZE 32
10 #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 debug
41 // for(index=0;g_argv[index];index++){
42 // printf("g_argv[%d]:%s\n",index,g_argv[index]);
43 // }
44 // 4.TODO
45 // 5.fork()
46 pid_t id=fork();
47 if(id==0){//子进程
48 printf("下面功能让子进程执行的\n");
49 execvp(g_argv[0],g_argv);//ls -a -l -i
50 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的别名,如果想让它成立,我们还需要做一些判断:
至此,圆满结束!!!