在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 -n54 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中直接拿pwd44 if(nullptr == getcwd(pwd,sizeof(pwd))) return "None";45 //拿到后還需要更新環境變量中的pwd46 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 -n70 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維護了兩張表(命令行參數表+環境變量表)
第三版拷貝了環境變量表,維護了環境變量表(exxcvpe),增加了內置命令