目录
第一版只能维护命令行参数表+创建子进程, 执行非内建命令
第一版的执行结果:
第二版能维护命令行参数表+执行cd命令 ,判断了是否是自建命令(mysell自己执行自建命令,可以对环境变量发生改变),子进程执行其他命令.
第二版执行结果:
第三版 模拟真实shell从系统文件中获取环境变量,维护命令行参数表+维护环境变量表(execvpe)
第三版运行结果:
在shell的命令行中输入命令,会有两种执行命令的途径
- shell自己执行
- shell创建子进程(fork ,exit ,waitpid,exec) ,子进程去执行
- shell自己执行的命令是自建命令(bulit command)
- 子进程执行的是非自建命令
第一版只能维护命令行参数表+创建子进程, 执行非内建命令
我们先创建了命令行提示符 ,获取了命令行的内容,维护了命令行参数表,创建了子进程进行命令的执行
1 #include<iostream>
2 #include<cstdio>
3 #include<stdlib.h>
4 #include<cstring>
5 #include<string>
6 #include<unistd.h>
7 #include<sys/types.h>
8 #include<sys/wait.h>
9
10 using namespace std;
11 const int charsize =1024;//命令行提示符的大小
12 const int gargvnum =64;//命令行参数列表的大小
13
14 //全局的
15 char* gargv[gargvnum];
16 int gargc;
17
18
19 int lastcode = 0;
20
21 string GetUsrName()
22 {
23 string name =getenv("USER");
24 return name.empty()? "None" : name;
25 }
26 string GetHostName()
27 {
28 string name =getenv("HOSTNAME");
29 return name.empty()? "None" : name;
30 }
31 string GetPwd()
32 {
33 string name =getenv("PWD");
34 return name.empty()? "None" : name;
35 }
36 string MakeCommandLine()
37 { //[root@hcss-ecs-1f3a lesson17]#
38 char CommandLine[charsize];
39 snprintf(CommandLine ,charsize,"[%s@%s %s]# ",\
40 GetUsrName().c_str(),GetHostName().c_str(),GetPwd().c_str());
41 return CommandLine;
42 }
43 void PrintCommandLine()//1.打印命令行提示符
44 {
45 printf("%s",MakeCommandLine().c_str());
46 fflush(stdout);
47 }
48
49
50 bool GetCommand(char Command_buff[] ,int size)//2.获取命令
51 {
52 //将命令输出到字符数组中
53 //ls -a -l -n
54 char*result =fgets(Command_buff,size,stdin);
55 if(!result)
56 {
57 return false;
58 }
59 Command_buff[strlen(Command_buff)-1]= 0;//fgets会将回车(\n)也输入,将回车置零
60 if(strlen(Command_buff) == 0) return false;//命令行就输入了回车,strlen遇0(\0)会停下来
61 return true;
62 }
63
64 void ParseCommand(char Command_buff[] ,int size)//3.分析命令
65 {
66 memset(gargv ,0,sizeof(gargv));
67 gargc=0;
68 const char* SEP =" ";
69 gargv[gargc++] = strtok(Command_buff ,SEP);//strtok 会在字符串中查找分隔符,并将分隔符替换为 \0,从而将字符串分割成多个
70 while((bool)(gargv[gargc++] = strtok(nullptr,SEP)));//strtok后续使用要用nullptr代替元字符串
71 gargc--;
72 }
73
74 void debug()
75 {
76 printf("argc: %d\n", gargc);
77 for(int i = 0; gargv[i]; i++)
78 {
79 printf("argv[%d]: %s\n", i, gargv[i]);
80 }
81 }
85 bool ExecuteCommand()//4.执行命令
86 {
87 pid_t id =fork();
88 if(id ==0)
89 {//子进程
90 int ret =execvp(gargv[0],gargv);
91 if(ret ==-1) cout<<"子进程出错\n"<<endl;
92 exit(1);
93 }
94 int status =0;
95 pid_t rid =waitpid(id,&status ,0);
96 if(rid >0)
97 {
98 if(WIFEXITED(status))
99 {
100 lastcode=WEXITSTATUS(status);
101 }
102 else lastcode =100;
103 return true;
104 }
105 return false;
106
107 }
108
109 int main()
110 {
111 char Command_buff[charsize];
112 while(true)
113 {
114 PrintCommandLine();//1.打印命令行提示符
115
116 if(!GetCommand(Command_buff,charsize))//2.获取命令
117 {
118 continue;
119 }
120 ParseCommand(Command_buff ,charsize);//3.分析命令
121 //debug();
122 ExecuteCommand();//4.执行命令
123 }
124 return 0;
125 }
第一版的执行结果:
- 第一版在执行cd ..时,是改变了子进程的cwd,子进程执行完又退了,无法影响下面的进程,cd.. 不能让子进程执行.
- 所以cd .. 或cd / 是自建命令 ,需要shell自己执行,以便可以影响到下面的进程(例如在/ 目录下创建文件)
第二版能维护命令行参数表+执行cd命令 ,判断了是否是自建命令(mysell自己执行自建命令,可以对环境变量发生改变),子进程执行其他命令.
在执行创建子进程前判断,命令是否是内建命令(是否是cd 命令),是否要创建子进程.
getpwd不再是从环境变量中拿,从pcb中拿(因为pcb中是实时的),拿完更新环境变量(命令行提示符每次运行都会刷新,借此来维护cd 后的环境变量).
#include<iostream>
2 #include<cstdio>
3 #include<stdlib.h>
4 #include<cstring>
5 #include<string>
6 #include<unistd.h>
7 #include<sys/types.h>
8 #include<sys/wait.h>
9
10 using namespace std;
11 const int charsize =1024;
12 const int gargvnum =64;
14 //全局的
15 char* gargv[gargvnum];
16 int gargc;
17
19
20
21 //全局的当前shell的工作路径(定义到全局不会被销毁)
22 char pwd[charsize];
23 char pwdenv[charsize];
24
25 int lastcode = 0;
26
27 string GetUsrName()
28 {
29 string name =getenv("USER");
30 return name.empty()? "None" : name;
31 }
32 string GetHostName()
33 {
34 string name =getenv("HOSTNAME");
35 return name.empty()? "None" : name;
36 }
string GetPwd()
38 {
39 //string name =getenv("PWD");
40 //return name.empty()? "None" : name;
41
42
43 //从pcb中直接拿pwd
44 if(nullptr == getcwd(pwd,sizeof(pwd))) return "None";
45 //拿到后还需要更新环境变量中的pwd
46 snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
47 putenv(pwdenv);
48 return pwd;
49
50
51 }
52 string MakeCommandLine()
53 { //[root@hcss-ecs-1f3a lesson17]#
54 char CommandLine[charsize];
55 snprintf(CommandLine ,charsize,"[%s@%s %s]# ",\
56 GetUsrName().c_str(),GetHostName().c_str(),GetPwd().c_str());
57 return CommandLine;
58 }
59 void PrintCommandLine()//1.打印命令行提示符
60 {
61 printf("%s",MakeCommandLine().c_str());
62 fflush(stdout);
63 }
64
65
66 bool GetCommand(char Command_buff[] ,int size)//2.获取命令
67 {
68 //将命令输出到字符数组中
69 //ls -a -l -n
70 char*result =fgets(Command_buff,size,stdin);
71 if(!result)
72 {
73 return false;
74 }
75 Command_buff[strlen(Command_buff)-1]= 0;//fgets会将回车(\n)也输入
76 if(strlen(Command_buff) == 0) return false;//strlen遇0(\0)会停下来
77 return true;
78 }
80 void ParseCommand(char Command_buff[] ,int size)//3.分析命令
81 {
82 memset(gargv ,0,sizeof(gargv));
83 gargc=0;
84 const char* SEP =" ";
85 gargv[gargc++] = strtok(Command_buff ,SEP);//strtok 会在字符串中查找分隔符,并将分隔符替换为 \0,从而将字符串分割成多个
86 while((bool)(gargv[gargc++] = strtok(nullptr,SEP)));//strtok后续使用要用nullptr代替元字符串
87 gargc--;
88 }
89
90 void debug()
91 {
92 printf("argc: %d\n", gargc);
93 for(int i = 0; gargv[i]; i++)
94 {
95 printf("argv[%d]: %s\n", i, gargv[i]);
96 }
97 }
98
99
100
101 bool ExecuteCommand()//4.执行命令
102 {
103 pid_t id =fork();
104 if(id ==0)
105 {//子进程
106 int ret =execvp(gargv[0],gargv);
107 if(ret ==-1) cout<<"子进程出错\n"<<endl;
108 exit(1);
109 }
110 int status =0;
111 pid_t rid =waitpid(id,&status ,0);
112 if(rid >0)
113 {
114 if(WIFEXITED(status))
115 {
116 lastcode=WEXITSTATUS(status);
117 }
118 else lastcode =100;
119 return true;
120 }
return false;
122
123 }
124
125 //内建命令的执行(调用函数,改变状态)
126 bool CheckandExecBuiltCommand()
127 { //使用穷举法找内建命令
128 if(0 == strcmp(gargv[0],"cd"))
129 {
130 if(gargc == 2)
131 {
132 chdir(gargv[1]);
133 }
134 return true;
135 }
136 return false;
137
138 }
139
140 int main()
141 {
142 char Command_buff[charsize];
143 while(true)
144 {
145 PrintCommandLine();//1.打印命令行提示符
146
147 if(!GetCommand(Command_buff,charsize))//2.获取命令
148 {
149 continue;
150 }
151 ParseCommand(Command_buff ,charsize);//3.分析命令
152 //debug();
153 //判断是否是内建命令,shell自己执行
154 if(CheckandExecBuiltCommand())//是内建命令并执行
155 {
156 continue;
157 }
158 //不是内建命令,创建子进程执行
159 ExecuteCommand();//4.执行命令
160 }
161 return 0;
162 }
第二版执行结果:
第三版 模拟真实shell从系统文件中获取环境变量,维护命令行参数表+维护环境变量表(execvpe)
- myshell前面的两版都是从系统shell中获取的环境变量表
- 实际上,系统shell开启时,是从系统文件中获取环境变量表,但是这个过程涉及shell脚本(比较难搞,意义不大),
- 所以我们用将系统shell的环境变量表手动拷贝到myshell中的过程来模拟系统shell开启时,是从系统文件中获取环境变量表
- 要让myshell执行的子进程的环境变量与myshell一致(不与系统shell一致)使用execvpe
- 系统的shell维护了两张表(命令行参数表+环境变量表)
第三版拷贝了环境变量表,维护了环境变量表(execvpe),增加了内置命令(export echo env)
设置了退出码(lastcode)
第三版运行结果:
#include<iostream>
2 #include<cstdio>
3 #include<stdlib.h>
4 #include<cstring>
5 #include<string>
6 #include<unistd.h>
7 #include<sys/types.h>
8 #include<sys/wait.h>
9
10 using namespace std;
11 const int charsize =1024;
12 const int gargvnum =64;
13 const int envnum =64;
14 //全局的
15 char* gargv[gargvnum];
16 int gargc;
17
18 char* genv[envnum];
19
20
21 //全局的当前shell的工作路径(定义到全局不会被销毁)
22 char pwd[charsize];
23 char pwdenv[charsize];
24
25 //全局的进程返回码
26 int lastcode = 0;
27
28 string GetUsrName()
29 {
30 string name =getenv("USER");
31 return name.empty()? "None" : name;
32 }
33 string GetHostName()
34 {
35 string name =getenv("HOSTNAME");
36 return name.empty()? "None" : name;
37 }
string GetPwd()
39 {
40 //string name =getenv("PWD");
41 //return name.empty()? "None" : name;
42
43
44 //从pcb中直接拿pwd
45 if(nullptr == getcwd(pwd,sizeof(pwd))) return "None";
46 //拿到后还需要更新环境变量中的pwd
47 snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
48 putenv(pwdenv);
49 return pwd;
50
51
52 }
53 string MakeCommandLine()
54 { //[root@hcss-ecs-1f3a lesson17]#
55 char CommandLine[charsize];
56 snprintf(CommandLine ,charsize,"[%s@%s %s]# ",\
57 GetUsrName().c_str(),GetHostName().c_str(),GetPwd().c_str());
58 return CommandLine;
59 }
60 void PrintCommandLine()//1.打印命令行提示符
61 {
62 printf("%s",MakeCommandLine().c_str());
63 fflush(stdout);
64 }
bool GetCommand(char Command_buff[] ,int size)//2.获取命令
68 {
69 //将命令输出到字符数组中
70 //ls -a -l -n
71 char*result =fgets(Command_buff,size,stdin);
72 if(!result)
73 {
74 return false;
75 }
76 Command_buff[strlen(Command_buff)-1]= 0;//fgets会将回车(\n)也输入
77 if(strlen(Command_buff) == 0) return false;//strlen遇0(\0)会停下来
78 return true;
79 }
80
81 void ParseCommand(char Command_buff[] ,int size)//3.分析命令
82 {
83 memset(gargv ,0,sizeof(gargv));
84 gargc=0;
85 const char* SEP =" ";
86 gargv[gargc++] = strtok(Command_buff ,SEP);//strtok 会在字符串中查找分隔符,并将分隔符替换为 \0,从而将字符串分割成多个
87 while((bool)(gargv[gargc++] = strtok(nullptr,SEP)));//strtok后续使用要用nullptr代替元字符串
88 gargc--;
89 }
90
91 void debug()
92 {
93 printf("argc: %d\n", gargc);
94 for(int i = 0; gargv[i]; i++)
95 {
96 printf("argv[%d]: %s\n", i, gargv[i]);
97 }
98 }
bool ExecuteCommand()//4.执行命令
103 {
104 pid_t id =fork();
105 if(id ==0)
106 {//子进程
107 int ret =execvpe(gargv[0],gargv,genv);
108 if(ret ==-1) cout<<"子进程出错\n"<<endl;
109 exit(1);
110 }
111 int status =0;
112 pid_t rid =waitpid(id,&status ,0);
113 if(rid >0)
114 {
115 if(WIFEXITED(status))//子进程正常的返回
116 {
117 lastcode=WEXITSTATUS(status);
118 }
119 else lastcode = 100;//子进程异常
120 return true;
121 }
122 return false;
123
124 }
125
126 void AddEnv(char* item)
127 {
128 int index =0;
129 while(genv[index])
130 {
131 index++;
132 }
133 genv[index]=(char*)malloc(strlen(item)+1);
134 strncpy(genv[index] ,item,strlen(item)+1);
135 genv[++index]= nullptr;
136 }
//内建命令的执行(调用函数,改变状态)
138 bool CheckandExecBuiltCommand()
139 { //使用穷举法找内建命令
140 if(0 == strcmp(gargv[0],"cd"))
141 {
142 if(gargc == 2)
143 {
144 chdir(gargv[1]);
145 lastcode= 0;
146 }
147 else
148 {
149 lastcode=1;
150 }
151 return true;
152 }
153 else if(0 ==strcmp(gargv[0] ,"export"))
154 {
155 if(gargc ==2)
156 {
157 AddEnv(gargv[1]);
158 lastcode = 0;
159 }
160 else
161 {
162 lastcode= 1;
163 }
164 return true;
165 }
166 else if(0 == strcmp(gargv[0],"env"))
167 {
168 for(int i=0 ;genv[i];i++)
169 {
170 printf("%s\n",genv[i]);
171
172 }
173 lastcode =0;
174 return true;
175 }
176 else if(0 ==strcmp(gargv[0] ,"echo"))
177 {
178 if(gargc ==2)
179 {
180 //echo $?
181 //echo $PATH
182 //echo hello
183 if(gargv[1][0] == '$')
184 {
185 if(gargv[1][1] == '?')
186 {
187 printf("%d\n",lastcode);
188 lastcode =0;
189 }
190 }
191 else
192 {
193 printf("%s\n",gargv[1]);
194 lastcode =0;
195 }
196 }
197 else
198 {
199 lastcode=1;
200 }
201 return true;
202
203 }
204 return false;
205
206 }
207
208 // 作为一个shell,获取环境变量应该从系统的配置来
209 // 我们今天就直接从父shell中获取环境变量
210 void IniEnv()
211 {
212 extern char** environ;
213 int index=0;
214 while(environ[index])
215 {
216 genv[index]=(char*)malloc(strlen(environ[index])+1);//strlen不含\0
217 strncpy(genv[index],environ[index],strlen(environ[index])+1);
218 index++;
219 }
220 genv[index]= nullptr;
221
222 }
223
224 int main()
225 {
226 IniEnv();
227 char Command_buff[charsize];
228 while(true)
229 {
230 PrintCommandLine();//1.打印命令行提示符
231
232 if(!GetCommand(Command_buff,charsize))//2.获取命令
233 {
234 continue;
235 }
236 ParseCommand(Command_buff ,charsize);//3.分析命令
237 //debug();
238 //判断是否是内建命令,shell自己执行
239 if(CheckandExecBuiltCommand())//是内建命令并执行
240 {
241 continue;
242 }
243 //不是内建命令,创建子进程执行
244 ExecuteCommand();//4.执行命令
245 }
246 return 0;
247 }