准备工作的知识
我们要模拟实现一个命令行解释器的话,需要运用进程替换的知识。我们用我,如花,王婆,实习生的例子来说:这里的“我”就是程序员,如花是操作系统,王婆是命令行解释器bash,实习生则是子进程,我们用户想要和操作系统交流的话,就需要通过bash,而命令行解释器(王婆)不会自己去执行我们的命令,而是会创建一个子进程(派实习生)去执行。这样的话,假如说子进程出了错误崩了,不会影响到父进程bash。
下面我们先贴代码:
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <sys/types.h>
6 #include <sys/wait.h>
7
8 #define NUM 128
9 #define SIZE 32
10
11 char command_line[NUM];
12
13 char* command_parse[SIZE];
14 int main()
15 {
16 while(1)
17 {
18 //1.获取命令
19 memset(command_line,'\0',sizeof(command_line));//初始化命令数组为全\0
20 printf("[cyf@centos-myshell]$");
21 fflush(stdout);
22 if(fgets(command_line,NUM-1,stdin)) //在数组中获取命令
23 {
24 command_line[strlen(command_line)-1]='\0';//'\n' -> '\0'
25 }
26 //2.加工命令
27 int index=0;
28 command_parse[index]=strtok(command_line," ");//以空格分隔命令以及选项
29 while(1)
30 {
31 index++;
32 command_parse[index]=strtok(NULL," ");
33 if(command_parse[index]==NULL)//当没有空格的时候,strtok函数会返回NULL,
34 //并导致command_parse数组最后的内容为NULL
35 {
36 break;
37 }
38 }
39 //3.执行命令
40 if(fork()==0)
41 {
42 execvp(command_parse[0],command_parse);
43 exit(1);//若父进程得到子进程退出码为1的话,说明子进程替换失败
44 }
45 int status=0;
46 pid_t ret = waitpid(-1,&status,0);
47 if(ret>0&&WIFEXITED(status))
48 {
49 printf("Exit Code:%d\n",WEXITSTATUS(status));
50 }
51 }
52 return 0;
53 }
结果如下:
模拟实现命令行解释器,上面的代码主要做三个任务。分别是获取命令(将命令用一个数组存储),加工命令(分割命令与选项),执行命令(通过进程替换,用子进程执行命令)。
我们会发现例如ls命令和原本的命令行解释器有些出入,我们写的命令行解释器没有显示颜色,这是因为shell执行ls指令的时候,会自动添加"-color=autp"的选项,我们可以用下面的代码,也添加上这个选项。
if(command_parse[0]!=NULL&&strcmp(command_parse[0],"ls")==0)
{
index++;
command_parse[index]="--color=auto";
}
现在就可以显示颜色了:
现在我们又出现了一个问题:当我们使用cd命令的时候,他并不像我们想的那样顺利执行,如图:
我们会发现,当前路径并不像我们所写的那样,指向上级目录。
对于这个问题,我们需要首先需要认识一下什么是当前工作目录:
我们通过ls /proc/24576 -al命令查看test进程的信息。
其中cwd代表当前程序的工作目录,exe代表当前执行的是磁盘中的哪个程序。
我们要明白:每个进程都有他自己的工作路径,我们在执行cd命令的时候,是先进行了fork(),创建了子进程,用子进程执行的cd命令,也就是说我们cd更改的是子进程的工作路径,跟父进程(myshell进程)没有关系,在子进程完成cd命令退出后,那么子进程的工作路径也就随之消失了。之后我们用pwd查看当前路径,也是先fork创建子进程,此时这个子进程刚刚被父进程创建出来,继承了父进程的性质,工作路径和父进程一致,而父进程的工作路径刚刚并没有发生任何变化,所以pwd打印出来的路径也没有任何变化。
而我们做cd命令的时候,我们内心期望改的是父进程的路径,我们要解决这个问题的话就需要不通过子进程修改工作目录,当我们发现第一个子串是cd的时候,就要直接更改工作目录,更改工作目录的工作就交给chdir函数来完成。
#include<unistd.h>
int chdir(const char* path);
这个函数只有路径一个参数,当我们使用cd命令的时候,cd后面跟着的就是我们要去的路径,所以我们就可以写出下面的代码:
if(command_parse[0]!=NULL&&strcmp(command_parse[0],"cd")==0)
{
if(command_parse[1]!=NULL)
{
chdir(command_parse[1]);
continue;
}
}
结果为:
实际上,cd这样没有创建子进程去执行的命令叫做内置命令,就是shell内的一个函数调用,也就是说内置命令是shell自己做的,而不是创建子进程执行。
这个shell还有很多不完善的地方,后面我们会边学习边继续完善这个命令行解释器。
最后贴上完整代码:
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <sys/types.h>
6 #include <sys/wait.h>
7
8 #define NUM 128
9 #define SIZE 32
10
11 char command_line[NUM];
12
13 char* command_parse[SIZE];
14 int main()
15 {
16 while(1)
17 {
18 //1.获取命令
19 memset(command_line,'\0',sizeof(command_line));//初始化命令数组为全\0
20 printf("[cyf@centos-myshell]$");
21 fflush(stdout);
22 if(fgets(command_line,NUM-1,stdin)) //在数组中获取命令
23 {
24 command_line[strlen(command_line)-1]='\0';//'\n' -> '\0'
25 }
26 //2.加工命令
27 int index=0;
28 command_parse[index]=strtok(command_line," ");//以空格分隔命令以及选项
29 if(command_parse[0]!=NULL&&strcmp(command_parse[0],"ls")==0)
30 {
31 index++;
32 command_parse[index]=(char*)"--color=auto";
33 }
34 while(1)
35 {
36 index++;
37 command_parse[index]=strtok(NULL," ");
38 if(command_parse[index]==NULL)//当没有空格的时候,strtok函数会返回NULL,
39 //并导致command_parse数组最后的内容为NULL
40 {
41 break;
42 }
43 }
44 if(command_parse[0]!=NULL&&strcmp(command_parse[0],"cd")==0)
45 {
46 if(command_parse[1]!=NULL)
47 {
48 chdir(command_parse[1]);
49 continue;
50 }
51 }
52 //3.执行命令
53 if(fork()==0)
54 {
55 execvp(command_parse[0],command_parse);
56 exit(1);//若父进程得到子进程退出码为1的话,说明子进程替换失败
57 }
58 int status=0;
59 pid_t ret = waitpid(-1,&status,0);
60 if(ret>0&&WIFEXITED(status))
61 {
62 printf("Exit Code:%d\n",WEXITSTATUS(status));
63 }
64 }
65 return 0;
66 }