1.理解命令行解释器
命令行解释器本质上就是一个 能解析我们输入的命令 并创建子进程进行进程程序替换去执行命令的一个进程!
命令行解释器
因此 只要我们熟练掌握了进程创建和进程等待,实现一个基础点的命令行解释器就信手拈来了!
大思路 :1. 解析标准输入的字符串
2. 分析出指令是否是必须命令行解释器自己执行的
3.创建子进程 子进程进行程序替换执行用户要求的指令 父进程进行进程等待,等待子进程结束。
4 回到步骤一
2代码实现
我们喜欢程序能一直执行,不停的解析执行命令 所以总体应该是一个死循环
接下来第一步 :打印提示符
这些信息分别对应 [用户名 内核版本 当前工作目录] 前两个直接是环境变量的内容 第三个需要稍做处理 代码如下
8 #define NUM 1024
9 #define SUB 128
10
11 char cmd_str[1024];//用于用户输入指令
12 char SUB_str[SUB];//用于分离指令和携带的选项
13
14 int main(){
15 //获取提示符信息
16 char pwd[SUB];//用于获取当前路径
17 memset(pwd,0,SUB);
18 strcpy(pwd,getenv("PWD"));
19 char*end=strtok(pwd,"/");
20 char*cur_dir=0;//切割出当前目录
21 while(end=strtok(NULL,"/")){
22 cur_dir=end;
23 }
24 while(1){
25 //打印提示符
26 printf("[%s@%s %s]$ ",getenv("USER"),getenv("HOSTNAME"),cur_dir);
27 fflush(stdout);
28 //用户输入指令
29
30 return 0;//测试时先跳出
31 }
32 }
效果:
第二步:读取命令
代码:
1 #include<unistd.h>
2 #include<stdio.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/wait.h>
6 #include<sys/types.h>
7
8 #define NUM 1024
9 #define SUB 128
10
11 char cmd_str[1024];//用于用户输入指令
12 char sub_str[SUB];//用于分离指令和携带的选项
13
14 int main(){
15 //获取提示符信息
16 char pwd[SUB];//用于获取当前路径
17 memset(pwd,0,SUB);
18 strcpy(pwd,getenv("PWD"));
19 char*end=strtok(pwd,"/");
20 char*cur_dir=0;//切割出当前目录
W> 21 while(end=strtok(NULL,"/")){
22 cur_dir=end;
23 }
24 while(1){
25 //打印提示符
26 printf("[%s@%s %s]$ ",getenv("USER"),getenv("HOSTNAME"),cur_dir);
27 fflush(stdout);
28 //用户输入指令
29 memset(cmd_str,0,NUM);//每次输入前清空上一条指令
30 fgets(cmd_str,NUM,stdin);//可能携带选项 所以需要按行读入 不能用scanf
31 cmd_str[strlen(cmd_str)-1]=0;//将回车去掉
32 printf("%s\n",cmd_str);//测试
33
34 }
35 }
效果:
第三步 分离指令 和 携带选项
代码:
更拿到当前目录操作类型 分离字串 效果就不演示了
28 //用户输入指令
29 memset(cmd_str,0,NUM);//每次输入前清空上一条指令
30 fgets(cmd_str,NUM,stdin);//可能携带选项 所以需要按行读入 不能用scanf
31 cmd_str[strlen(cmd_str)-1]=0;//将回车去掉
32 //分离指令和选项
33 sub_str[0]=strtok(cmd_str," ");//拿到指令
34 for(int i=1;sub_str[i]=strtok(NULL," ");i++){;}//拿到选项
第四步:对内建指令做特殊处理
针对一些要对命令行解释器进程本身做操作的指令 我们不创建子进程直接进行系统调用完成
10 #define ENV_BUFFER 1024
13 char env_buffer[ENV_BUFFER];//用于储存要增加的环境变量
32 //分离指令和选项
33 sub_str[0]=strtok(cmd_str," ");//拿到指令
34 for(int i=1;sub_str[i]=strtok(NULL," ");i++){;}//拿到选项
35 //针对内建指令
36 if(!strcmp(sub_str[0],"cd")&&sub_str[1]){ //cd
37 chdir(sub_str[1]);
38 continue;
39 }
43 if(!strcmp(sub_str[0],"export")&&sub_str[1]){//增加环境变量
44 strcpy(env_buffer,sub_str[1]);//因为每次sub_str会被修改
45 putenv(env_buffer);//环境变量需要一块独立的空间
46 continue;
47 }
//...就不一一实现了
第五步:创建子进程进行程序替换执行指令 父进程阻塞等待
这里就是进程替换和进程等待的实践运用,也是这个程序的核心代码
代码:
//...就不一一实现了
45 //创建子进程
46 pid_t id =fork();
47 if(!id){
48 //child
49 //程序替换
50 execvp(sub_str[0],sub_str);
51 //如果指令有误才会执行下面的语句
52 printf("-myshell: %s: command not found\n",sub_str[0]);
53 continue;
54 }
55 else if(id>0){
56 //father
57 int status=0;
58 int ret=0;
59 ret =waitpid(id,&status,0);//阻塞等待子进程
60 if(ret){
61 //调试信息
62 // printf("等待成功 ");
63 // if(WIFEXITED(status))
64 // printf( "退出码:%d\n",(status>>8)&0xFF);
65 // else
66 // printf("信号:%d\n",status&0x7F);
67 //
68 // }
69 // else{
70 // printf("等待失败!\n");
71 // exit(1);
72 // }
73 }
74 else{
75 printf("创建进程失败!\n");
76 exit(2);
77 }
78 }
效果:基本上就已经大功告成了!
第六步 完善细节
这样实现的ls没有颜色 是因为没有带任何选项 而bash的ls是默认带了颜色选项的
bash的ls
我们的 ls
解决方式就是对ls特殊处理
代码:
32 //分离指令和选项
33 sub_str[0]=strtok(cmd_str," ");//拿到指令
34 int i=1;//对ls 等指令增加默认选项
35 if(!strcmp(sub_str[0],"ls"))
36 sub_str[i++]="--color=auto";
结果: