1.打印提示符并获取命令行
我们在使用shell的时候,发现我们在输入命令是,前面会有:有用户名,版本,当前路径等信息,这里我们可以用环境变量去获取:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 const char* getUsername()
5 {
6 const char* name = getenv("USER");
7 if(name) return name;
8 else return "none";
9 }
10
11 const char* getHostname()
12 {
13 const char* hostname = getenv("HOSTNAME");
14 if(hostname) return hostname;
15 else return "none";
16 }
17
18 const char* getCwd()
19 {
20 const char* cwd = getenv("PWD");
21 if(cwd) return cwd;
22 else return "none";
23 }
24
25 int main()
26 {
27 printf("%s@%s %s\n",getUsername(),getHostname(),getCwd());
28 return 0;
29 }
写。
看到我们打印出来的是绝对路径, 而shell显示的相对路径, 但为了区分先这样不去裁剪.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 #define NUM 1024
6
7 const char* getUsername()
8 {
9 const char* name = getenv("USER");
10 if(name) return name;
11 else return "none";
12 }
13
14 const char* getHostname()
15 {
16 const char* hostname = getenv("HOSTNAME");
17 if(hostname) return hostname;
18 else return "none";
19 }
20
21 const char* getCwd()
22 {
23 const char* cwd = getenv("PWD");
24 if(cwd) return cwd;
25 else return "none";
26 }
27
28 int getUsercommand(char* command, int num)
29 {
30 printf("[%s@%s %s]",getUsername(),getHostname(),getCwd());
31 char* r = fgets(command,num,stdin);//最终还是会输入\n
32 if(r == NULL) return 1;
33
34 command[strlen(command)-1] = '\0';//去除输入的换行
35 return 0;
36 }
37
38 int main()
39 {
40 char usercommand[NUM];
41 //1.打印提示符并且获取命令字符串
42 getUsercommand(usercommand,sizeof(usercommand));
43 //2.
44 //3.
45 printf("%s",usercommand);//回显命令,用于测试
46 return 0;
47 }
由于用scanf接收的遇到空格就会停止读取, 所以用fgets, 而且用户输入完命令一定会输入回车, 所以把最后一个回车符删掉.
2.解析命令行
我们在输入命令时, 可能不仅仅只是一段,比如说:"ls -a -l "。但是命令行解释器内部在解析指令时应该传递的是"ls" "-a" "-l"这样的多个个字符串, 所以我们还需要以空格来分割字符串。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 #define DEBUG 1
6 #define NUM 1024
7 #define SIZE 64
8 #define SEP " "
41 void commandSplit(char* in, char* out[])
42 {
43 int argc = 1;
44 out[0] = strtok(in,SEP);
W> 45 while(out[argc++] = strtok(NULL,SEP));//报警不需要处理
46
47 #ifdef DEBUG
48 for(int i = 0; out[i]; i++)
49 printf("%d:%s\n",i,out[i]);
50 #endif
51 }
52
53 int main()
54 {
55 char usercommand[NUM];
56 char* argv[SIZE];
57 //1.打印提示符并且获取命令字符串
58 getUsercommand(usercommand,sizeof(usercommand));
59 //2.分割字符串
60 commandSplit(usercommand, argv);
61 //3.
62 return 0;
63 }
3.执行对应的命令
创建子进程和进程替换, 为了不影响shell, 我们将大部分指令的执行让子进程去完成, 父进程只要阻塞等待子进程完成就好了。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include <sys/wait.h>
7
8 //#define DEBUG 1
9 #define NUM 1024
10 #define SIZE 64
11 #define SEP " "
12
13 const char* getUsername()
14 {
15 const char* name = getenv("USER");
16 if(name) return name;
17 else return "none";
18 }
19
20 const char* getHostname()
21 {
22 const char* hostname = getenv("HOSTNAME");
23 if(hostname) return hostname;
24 else return "none";
25 }
26
27 const char* getCwd()
28 {
29 const char* cwd = getenv("PWD");
30 if(cwd) return cwd;
31 else return "none";
32 }
33
34 int getUsercommand(char* command, int num)
35 {
36 printf("[%s@%s %s]",getUsername(),getHostname(),getCwd());
37 char* r = fgets(command,num,stdin);//最终还是会输入\n
38 if(r == NULL) return -1;
39
40 command[strlen(command)-1] = '\0';//去除输入的换行
41 return strlen(command);
42 }
43
44 void commandSplit(char* in, char* out[])
45 {
46 int argc = 1;
47 out[0] = strtok(in,SEP);
W> 48 while(out[argc++] = strtok(NULL,SEP));//报警不需要处理
49
50 #ifdef DEBUG
51 for(int i = 0; out[i]; i++)
52 printf("%d:%s\n",i,out[i]);
53 #endif
54 }
55
56 int execute(char* argv[])
57 {
58 pid_t id = fork();
59 if(id < 0) return 1;
60 else if(id == 0)
61 {
62 //child
63 //exec commond
64 execvp(argv[0],argv);
65 exit(1);
66 }
67
68 else
69 {
70 //father
71 pid_t rid = waitpid(id,NULL,0);
72 if(rid < 0)
73 printf("wait fail\n");
74 }
75
76 return 0;
77 }
78
79 int main()
80 {
81 while(1)
82 {
83 char usercommand[NUM];
84 char* argv[SIZE];
85 //1.打印提示符并且获取命令字符串
86 int n = getUsercommand(usercommand,sizeof(usercommand));
87 if(n <= 0) continue;//如果得到的是空串或者获取失败,不要往后执行
88 //2.分割字符串
89 commandSplit(usercommand, argv);
90 //3.执行命令
91 execute(argv);
92 }
93 return 0;
94 }
由于shell要一直运行, 所以要循环执行, 这里程序替换用execvp函数比较合适, 因为argv数组就是我们分割出的一个个命令的子串, argv[0]就是程序名, argv就是指令集. 父进程只进行wait即可.
此外, getUsercommand函数可以优化一下, 返回的是输入的指令的长度, 如果接收失败(返回值为-1或者返回值是0只打印了空行)就不需要往下执行了, 直接continue进行下一轮.
4.特殊处理
有一批命令, 不能让子进程执行, 必须让父进程自己执行, 这些命令叫内建命令.
1) cd指令
可以看到cd .. 之后并没有发生什么异常, 但是pwd之后发现路径没有发生变化.
我们为什么能在linux中进入某个目录, 就是因为我们改变了shell的工作目录. 每个进程都有自己的工作目录, 我们想让父进程的工作目录发生改变, 但是程序替换之后都是子进程在执行cd .., 改变的都是子进程的工作目录, 子进程改变完了又被回收了, 父进程完全没发生变化, 所以cd应该实现成内建命令。
79 void cd(const char* path)
80 {
81 chdir(path);
82 }
83
84 //1->yes,0->no
85 int doBuildin(char* argv[])
86 {
87 if(strcmp(argv[0],"cd") == 0)
88 {
89 char* path = NULL;
W> 90 if(argv[1] == NULL) path = ".";
91 else path = argv[1];
92 cd(path);
93 return 1;
94 }
95 else if(strcmp(argv[0],"ls")==0)
96 {
97 return 1;
98 }
99 return 0;
100 }
101
102 int main()
103 {
104 while(1)
105 {
106 char usercommand[NUM];
107 char* argv[SIZE];
108 //1.打印提示符并且获取命令字符串
109 int n = getUsercommand(usercommand,sizeof(usercommand));
110 if(n <= 0) continue;//如果得到的是空串或者获取失败,不要往后执行
111 //2.分割字符串
112 commandSplit(usercommand, argv);
113 //3.检查是不是内建命令,是的话直接执行
114 n = doBuildin(argv);
115 if(n) continue;//是内建命令不用往后执行了
116 //4.执行命令
117 execute(argv);
118 }
119 return 0;
120 }
所以在执行命令前先检查是不是内建命令, 用返回值接收, 如果是就直接执行并返回1, continue不往下执行, 如果不是就返回0, 执行命令.
既然当前的工作目录改变了, 那么环境变量PWD也要改变:
chdir改变当前工作目录, getcwd获取当前的工作路径, sprintf将tmp中的内容输出到cwd中, putenv将cwd导入环境变量.
2) export命令
创建一个数组env储存要导入的环境变量, 设置size指向导入到第几个环境变量.
如果argv[1]是空就直接返回, 否则就导入环境变量, 注意不能直接把argv[1]导入进去, 因为argv[1]随着指令的输入时刻在变化, 需要开辟额外的空间去存储.
3)echo指令
4)ls指令
我们执行的ls指令中不同的文件都有不同的颜色,所以对于ls我们可以在分割命令的时候加上一个“--color=auto”.