简单的shell,其功能有:解释执行命令,支持输入输出重定向,支持管道,后台运行程序
支持的命令格式:
单个命令:如 ls
带1到多个参数的命令:如ls -l /tmp
带一个输出重定向或输入重定向或管道的命令:如ls -l > a, wc -c < a, ls -l / | wc -c
后台运行符&可加在以上各个命令的最后面,如ls &
输入exit或logout退出myshell
1 #include <stdio.h> 2 #include <stdlib.h> // exit 3 #include <string.h> 4 #include <fcntl.h> // O_RDWR 5 #include <dirent.h> // DIR, struct dirent 6 7 #define BUFFSIZE 64 8 9 #define normal 0 // 一般的命令 10 #define out_redirect 1 // 输出重定向 11 #define in_redirect 2 // 输入重定向 12 #define have_pipe 3 // 命令中有管道 13 14 void print_prompt(); // 打印提示符 15 void get_input(char *); // 读取用户输入的命令 16 // 二维数组作为函数参数,第1维长度可以不指定,但必须指定第2维长度 17 // 也可使用指向含8个元素一维数组的指针,char (*a)[8] 18 void explain_input(char *, int *, char (*a)[8]); // 解析输入的命令 19 void do_cmd(int, char a[][8]); // 执行命令 20 int find_command(char *); // 查找命令中的可执行程序 21 22 int main(int argc, char *argv[]) 23 { 24 char buf[BUFFSIZE]; 25 char arglist[10][8]; 26 int argcount; 27 28 while(1) 29 { 30 memset(buf, 0, BUFFSIZE); 31 print_prompt(); // 打印提示符 32 get_input(buf); // 读取用户输入 33 if(strcmp(buf, "exit")==0 || strcmp(buf, "logout")==0) 34 break; 35 int i; 36 for(i=0; i<10; i++) // 初始化存放命令及其参数的数组 37 arglist[i][0] = '\0'; 38 argcount = 0; // 计数,命令中词汇数量 39 explain_input(buf, &argcount, arglist); // 解析命令 40 do_cmd(argcount, arglist); // 执行命令 41 } 42 return 0; 43 } 44 45 void print_prompt() 46 { 47 printf("myshell$$ "); 48 // fflush(stdout); // fflush 冲洗stdout 49 } 50 51 void get_input(char *buf) 52 { 53 fgets(buf, BUFFSIZE, stdin); // fgets 54 int len = strlen(buf); // 计入换行符\n,不计入结束符\0 55 if(len >= BUFFSIZE) 56 { 57 printf("ERROR: command is too long !\n"); 58 exit(-1); 59 } 60 buf[len-1] = '\0'; // 去除读入的换行符 61 } 62 63 // 解析buf中的命令,结果存入arglist中,命令及其参数个数为argcount 64 // 如,"ls -l"命令,则arglist[0]、arglist[1]分别为ls、-l 65 void explain_input(char *buf, int *argcount, char arglist[][8]) 66 { 67 char *p = buf; 68 69 // 将用户输入的整串字符串拆分为一个个单词 70 // 存入二维数组的每一行中 71 while(*p != '\0') 72 { 73 if(*p == ' ') 74 p++; 75 else 76 { 77 char *q = p; 78 int len = 0; // 单词长度 79 while((*q!=' ') && (*q!='\0')) 80 { 81 q++; len++; 82 } 83 // 将当前拆解的单词存入二维数组中的一行 84 strncpy(arglist[*argcount], p, len+1); 85 arglist[*argcount][len] = '\0'; 86 (*argcount)++; 87 p = q; 88 } 89 } 90 } 91 92 // 93 void do_cmd(int argcount, char arglist[10][8]) 94 { 95 // 指针数组,每个元素指向二维数组中的一行 96 // arg存放所有命令及其参数,argnext存放管道符后的命令 97 char *arg[argcount+1], *argnext[argcount+1]; 98 int i, flag = 0, how = 0, background = 0; 99 char *file; 100 pid_t pid; 101 102 // 提取命令 103 for(i=0; i<argcount; i++) 104 arg[i] = arglist[i]; 105 arg[argcount] = NULL; 106 107 // 查看命令行是否有后台运行符 108 for(i=0; i<argcount; i++) 109 { 110 if(strncmp(arg[i], "&", 1) == 0) // strncmp 111 { // 后台运行符必须在命令的末尾,否则命令格式错误 112 if(i == argcount-1) 113 { 114 background = 1; 115 arg[argcount-1] = NULL; 116 break; 117 } 118 else 119 { 120 printf("ERROR: wrong command about backgrount\n"); 121 return; 122 } 123 } 124 } 125 126 for(i=0; arg[i]!=NULL; i++) 127 { 128 if(strcmp(arg[i], ">") == 0) 129 { 130 flag++; 131 how = out_redirect; 132 if(arg[i+1] == NULL) // 输出重定向符在最后面 133 flag++; // 使flag大于1,告知命令格式错误 134 } 135 if(strcmp(arg[i], "<") == 0) 136 { 137 flag++; 138 how = in_redirect; 139 if(i == 0) // 输入重定向符在最前面 140 flag++; 141 } 142 if(strcmp(arg[i], "|") == 0) 143 { 144 flag++; 145 how = have_pipe; 146 if(arg[i+1] == NULL) // 管道符在最后面 147 flag++; 148 if(i == 0) // 管道符在最前面 149 flag++; 150 } 151 } 152 // flag大于1,说明同时含有>,<,|中的两个或以上,本程序不支持 153 // 或者命令格式错误 154 if(flag > 1) 155 { 156 printf("ERROR: wrong command about >,<,|\n"); 157 return; 158 } 159 160 if(how == out_redirect) // 命令中只含有一个输出重定向符 161 { 162 for(i=0; arg[i]!=NULL; i++) 163 if(strcmp(arg[i], ">") == 0) 164 { 165 file = arg[i+1]; // 获取输出重定向的文件名 166 arg[i] = NULL; 167 } 168 } 169 170 if(how == in_redirect) // 命令中只含有一个输入重定向符 171 { 172 for(i=0; arg[i] != NULL; i++) 173 if(strcmp(arg[i], "<") == 0) 174 { 175 file = arg[i+1]; 176 arg[i] = NULL; 177 } 178 } 179 180 if(how == have_pipe) // 命令中只含有一个管道符号 181 { 182 for(i=0; arg[i]!=NULL; i++) 183 if(strcmp(arg[i], "|") == 0) 184 { 185 arg[i] = NULL; 186 i++; 187 int j = 0; 188 // 将管道符后面的命令存入argnext中 189 while(arg[i] != NULL) 190 { 191 argnext[j++] = arg[i++]; 192 } 193 argnext[j] = NULL; 194 break; 195 } 196 } 197 198 pid = fork(); // 创建子进程 199 if(pid < 0) 200 { 201 perror("fork failure"); 202 return; 203 } 204 205 switch(how) 206 { 207 case 0: // 一般命令 208 if(pid==0) // 子进程执行用户输入的命令 209 { 210 if(!find_command(arg[0])) // 判断命令是否可执行 211 { 212 printf("%s: command not found\n", arg[0]); 213 exit(0); 214 } 215 execvp(arg[0], arg); // execvp 开始执行命令 216 exit(0); 217 } 218 break; 219 case 1: // 命令中含有输出重定向符 220 if(pid == 0) 221 { 222 if(!find_command(arg[0])) 223 { 224 printf("%s: command not found\n", arg[0]); 225 exit(0); 226 } 227 // 打开或新建输出重定向的文件 228 int fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0644); 229 // 将标准输出复制到打开的文件描述符,即用文件描述符替换标准输出 230 dup2(fd, 1); // dup2(int oldfd, int newfd) 231 execvp(arg[0], arg); // execvp 232 exit(0); 233 } 234 break; 235 case 2: // 命令中含有输入重定向符 236 if(pid == 0) 237 { 238 if(!find_command(arg[0])) 239 { 240 printf("%s: command not found\n", arg[0]); 241 exit(0); 242 } 243 int fd = open(file, O_RDONLY); 244 dup2(fd, 0); 245 execvp(arg[0], arg); 246 exit(0); 247 } 248 break; 249 case 3: // 命令中含有管道符 250 if(pid == 0) // 子进程 251 { 252 pid_t pid2; 253 int fd2; 254 if((pid2=fork()) < 0) // 当前子进程中在新建一个子进程 255 { 256 perror("fork2 failure"); 257 return; 258 } 259 if(pid2 == 0) // 新建的子进程执行管道符前面的命令 260 { 261 if(!find_command(arg[0])) 262 { 263 printf("%s: command not found\n", arg[0]); 264 exit(0); 265 } 266 // 将管道符前的命令执行结果存入fd2中 267 fd2 = open("/tmp/youdontknowfile", 268 O_WRONLY | O_CREAT | O_TRUNC, 0644); 269 dup2(fd2, 1); // 重定向标准输出 270 execvp(arg[0], arg); 271 exit(0); 272 } 273 waitpid(pid2, NULL, 0); // 等待管道符前的命令执行返回 274 if(!find_command(argnext[0])) 275 { 276 printf("%s: command not found\n", argnext[0]); 277 exit(0); 278 } 279 fd2 = open("/tmp/youdontknowfile", O_RDONLY); 280 dup2(fd2, 0); // 将fd2定义为标准输入 281 execvp(argnext[0], argnext); // 执行管道符后面的命令 282 exit(0); 283 } 284 //remove("/tmp/youdontknowfile"); 285 //unlink("/tmp/youdontknowfile"); 286 break; 287 default: 288 break; 289 } 290 // 命令中有后台运行符,则父进程直接返回,不等待子进程返回 291 if(background == 1) 292 { 293 printf("[process id %d]\n", pid); 294 return; 295 } 296 waitpid(pid, NULL, 0); // waitpid 父进程等待子进程返回 297 } 298 299 // 判断命令是否可执行,是否有对应的可执行文件 300 int find_command(char *command) 301 { 302 DIR *dir; 303 struct dirent *ptr; 304 char *path[] = {"./", "/bin", "/usr/bin", NULL}; 305 // 当输入命令"./build"时,将build命令与目录中的build文件进行匹配 306 if(strncmp(command, "./", 2) == 0) 307 command = command + 2; 308 int i = 0; 309 while(path[i] != NULL) 310 { 311 if((dir=opendir(path[i])) == NULL) // 打开目录 312 printf("cannot open /bin\n"); 313 while((ptr=readdir(dir)) != NULL) // 读取目录中的文件列表 314 if(strcmp(ptr->d_name, command) == 0) 315 { 316 closedir(dir); 317 return 1; 318 } 319 closedir(dir); 320 i++; 321 } 322 return 0; 323 }