├── README.md └── shell.c /README.md: -------------------------------------------------------------------------------- 1 | # 一、实验简介 2 | 3 | > 《C 语言实现 Linux Shell 命令解释器》项目可以培养 Linux 系统编程能力,尤其是在多进程方面。可以了解fork、execvp 等重要的系统调用。另外还能深入到底层理解 Linux Shell 的功能的实现手段。为了测试方便,代码只放在一个文件中。 4 | 5 | ## 1.1 知识点 6 | - Shell 的基本概念 7 | - 进程控制相关的系统调用的使用(如 fork,exev) 8 | - 信号的概念及系统调用 signal的使用 9 | 10 | ## 1.2 效果截图 11 | ![这里写图片描述](http://img.blog.csdn.net/20171016141912089?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvel9tbDExOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 12 | ## 1.3 设计流程 13 | ![这里写图片描述](http://img.blog.csdn.net/20171016145603602?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvel9tbDExOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 14 | # 二、main 函数的设计 15 | 首先,以自顶向下的方式探讨一下在 Linux Shell 的周期内主要做了哪些事: 16 | 初始化:在这一步,一个典型的 Shell 应该读取配置文件并执行配置功能。这样可以改变 Shell 的行为。 17 | 解释:接下来,Shell 将从标准输入(可以是交互的方式或者一个脚本文件)中读入命令,然后执行它。 18 | 终止:在命令被执行之后,Shell 执行关闭命令,释放内存,最后终止。 19 | 详细见代码注释: 20 | ``` 21 | /* 主函数入口 */ 22 | /* 主函数入口 */ 23 | int main() 24 | { 25 | char *command; 26 | int iter = 0; 27 | command = (char *)malloc(MAX+1); //用于存储命令语句 28 | 29 | chdir("/home/"); /* 通常在登陆 shell 的时候通过查找配置文件(/etc/passwd 文件) 30 | 中的起始目录,初始化工作目录,这里默认为家目录 */ 31 | while(1) 32 | { 33 | iter = 0; 34 | //定义信号处理方式 35 | signal_set(); 36 | //用于输出提示符 37 | print_prompt(); 38 | //扫描多条命令语句 39 | scan_command(command); 40 | // 基于分号解析出单条命令语句,并以字符串的形式存进全局变量 all 中 41 | parse_semicolon(command); 42 | 43 | // 迭代执行单条命令语句 44 | while(all[iter] != NULL) 45 | { 46 | execute(all[iter]); //这是 shell 解释器的核心函数 47 | iter += 1; 48 | } 49 | } 50 | } 51 | ``` 52 | - 注:print_prompt()、scan_command(command) 和 parse_semicolon(command) 三个函数是自定义函数,功能分别为:用于输出提示符,扫描多条命令语句,基于分号解析出单条命令语句,并以字符串的形式存进全局变量 all 中。概念简单,其详细代码在项目完整代码中。 53 | 54 | # 三、程序的核心功能 execute 函数 55 | execute函数完成的主要功能如下: 56 | - 判断用户执行的是否是 Shell 的内建命令,如果是则执行(这里为了强调概念,没有过多的添加内建命令) 57 | - 解析单条命令语句,即将命令名和命令参数解析并保存在字符串数组,以便调用 bf_exec命令。 58 | - 识别输入输出重定向符号,利用底层系统调用函数 dup2完成上层输入输出重定向 59 | - 识别后台运行符号,通过设置自定义函数 bf_exec的模式为1实现,这里要说明一点:所谓后台执行,无非就是让前台 Shell 程序以非阻塞的形式执行,并没有对后台程序做任何操作 60 | - 这部分的完整代码如下: 61 | ``` 62 | /* 执行单条命令行语句 */ 63 | void execute(char *command) 64 | { 65 | char *arg[MAX_COMM]; 66 | char *try; 67 | arg[0] = parse(command, 0); //获得命令名称的字符串指针,如“ls” 68 | int t = 1; 69 | arg[t] = NULL; 70 | if (strcmp(arg[0], "cd") == 0) // 处理内嵌命令“cd”的情况 71 | { 72 | try = parse(command, 1); 73 | cd(try); 74 | return ; 75 | } 76 | if (strcmp(arg[0], "exit") == 0) // 为了方便用户推出shell 77 | exit(0); 78 | 79 | // 循环检测剩下的命令参数,即检测是否:重定向?管道?后台执行?普通命令参数? 80 | while (1) 81 | { 82 | try = parse(command, 1); 83 | if (try == NULL) 84 | break; 85 | else if (strcmp(try, ">") == 0) // 重定向到一个文件的情况 86 | { 87 | try = parse(command, 1); // 得到输出文件名 88 | file_out(arg, try, 0); // 参数 0 代表覆盖的方式重定向 89 | return; 90 | } 91 | else if (strcmp(try, ">>") == 0) // 追加重定向到一个文件 92 | { 93 | try = parse(command, 1); 94 | file_out(arg, try, 1); // 参数 1 代表追加的方式重定向 95 | return; 96 | } 97 | else if (strcmp(try, "<") == 0) // 标准输入重定向 98 | { 99 | try = parse(command, 1); // 输入重定向的输入文件 100 | char *out_file = parse(command, 1); // 输出重定向的输出文件 101 | if (out_file != NULL) 102 | { 103 | if (strcmp(out_file, ">") == 0) // 输入输出文件给定 104 | { 105 | out_file = parse(command, 1); 106 | if (out_file == NULL) 107 | { 108 | printf("Syntax error !!\n"); 109 | return; 110 | } 111 | else 112 | file_in(arg, try, out_file, 1); // 参数 1 针对双重定向 < > 113 | } 114 | else if (strcmp(out_file, ">>") == 0) 115 | { 116 | out_file = parse(command, 1); 117 | if (out_file == NULL) 118 | { 119 | printf("Syntax error !!\n"); 120 | return; 121 | } 122 | else 123 | file_in(arg, try, out_file, 2); // 参数 2 针对双重定向 < >> 124 | } 125 | } 126 | else 127 | { 128 | file_in(arg, try, NULL, 0); // 模式 0 针对单一的输入重定向 129 | } 130 | } 131 | //处理后台进程 132 | else if (strcmp(try, "&") == 0) // 后台进程 133 | { 134 | bf_exec(arg, 1); // bf_exec 的第二个参数为 1 代表后台进程 135 | return; 136 | } 137 | else //try是一个命令参数 138 | { 139 | arg[t] = try; 140 | t += 1; 141 | arg[t] = NULL; 142 | } 143 | } 144 | 145 | bf_exec(arg, 0); // 参数 0 表示前台运行 146 | return; 147 | } 148 | ``` 149 | ## 3.1处理标准输出重定向问题的函数: file_out 150 | 本函数的定义为: **void file_out(char \*arg[], char \*out_file, int type)。** arg 代表命令行参数。 **out_file** 代表重定向的文件名。 通过** dup2(f, 1)** 语句使得标准输出文件(文件描述符为1)重定向到制定的文件**out_file**(文件描述符f)。至于是追加重定向还是覆盖重定向取决于函数 open 的打开模式(是否是 **O_APPEND**)。 151 | 这部分的完整代码如下: 152 | ``` 153 | /* 处理输出重定向文件的问题 */ 154 | void file_out(char *arg[], char *out_file, int type) 155 | { 156 | int f; 157 | current_out = dup(1); 158 | if(type == 0) //处理以覆盖的方式重定向“>” 159 | { 160 | f = open(out_file, O_WRONLY | O_CREAT, 0777); 161 | dup2(f, 1); //复制文件描述符f,并指定为 1,也就是让标准输出重定向到指定的文件 162 | close(f); 163 | bf_exec(arg, 0); 164 | } 165 | else // 处理以追加的方式重定向“>>” 166 | { 167 | f = open(out_file, O_WRONLY | O_CREAT | O_APPEND , 0777); //以 O_APPEND 模式打开 168 | dup2(f, 1); 169 | close(f); 170 | bf_exec(arg, 0); 171 | } 172 | } 173 | ``` 174 | ## 3.2 处理标准输入重定向问题的函数: file_in 175 | 本函数的定义原型为 **void file_in(char \*arg[], char \*in_file, char \*out_file, int type)** 类似于 file_out函数,该本分仍然采用 dup2(in, 0)来完成标准输入文件(文件描述符0)重定向到执行的文件 in_file(文件描述符为 in)。 176 | - 这部分的完整代码如下: 177 | ``` 178 | /* 处理输入重定向文件的问题 */ 179 | void file_in(char *arg[], char *in_file, char *out_file, int type) 180 | { 181 | int in; 182 | in = open(in_file, O_RDONLY); 183 | current_in = dup(0); // 184 | dup2(in, 0); 185 | close(in); 186 | if(type == 0) // 单一的输入重定向 187 | { 188 | printf("Going to execute bf_exec\n"); //debug remove it 189 | bf_exec(arg, 0); 190 | } 191 | else if(type == 1) // 双重重定向 `< ... >` 192 | { 193 | file_out(arg, out_file, 0); 194 | } 195 | else // 双重重定向 `< ... >>` 196 | { 197 | file_out(arg, out_file, 1); 198 | } 199 | return; 200 | } 201 | ``` 202 | 203 | ## 3.3 实现命令执行的最后一步: bf_exec 204 | 本函数定义的原型为:**void bf_exec(char \*arg[], int type)**, arg 指命令行,type标识前台还是后台运行。 205 | - 实现命令的执行必须在 Shell 父进程下 fork 一个子进程,并在子进程中调用 execvp 函数装载子进程执行的代码(所要执行的命令)。 206 | - 程序的前后台执行取决于父进程在子进程执行过程中是否是阻塞的。代码中,前台进程的父进程需要调用 wait(&pid) 阻塞,等待前台执行的命令执行完才能唤醒。 207 | - 这部分的完整代码如下: 208 | ``` 209 | /* 创建子进程,调用 execvp 函数执行命令程序(前后台)*/ 210 | void bf_exec(char *arg[], int type) 211 | { 212 | pid_t pid; 213 | if(type == 0) // 前台执行 214 | { 215 | if((pid = fork()) < 0) 216 | { 217 | printf("*** ERROR: forking child process failed\n"); 218 | return ; 219 | } 220 | // 父子进程执行代码的分离 221 | else if(pid == 0) //子进程 222 | { 223 | signal(SIGTSTP, SIG_IGN); 224 | signal(SIGINT,SIG_DFL);//用于杀死子进程 225 | signal(SIGQUIT, SIG_IGN); 226 | execvp(arg[0], arg); // execvp 用于在子进程中替换为另一段程序 227 | } 228 | else //父进程 229 | { 230 | signal(SIGCHLD, bg_signal_handle); 231 | signal(SIGINT, sig_handle); // SIGINT = 2, 用户键入Ctrl-C 232 | signal(SIGQUIT, sig_handle); /* SIGQUIT = 3 用户键入Ctr+\ */ 233 | signal(SIGTSTP, sig_handle); // SIGTSTP =20,一般有Ctrl-Z产生 234 | pid_t c; 235 | c = wait(&pid); //等待子进程结束 236 | dup2(current_out, 1); //还原默认的标准输出 237 | dup2(current_in, 0); //还原默认的标准输入 238 | return; 239 | } 240 | } 241 | else // 后台执行 242 | { 243 | if((pid = fork()) < 0) 244 | { 245 | printf("*** ERROR: forking child process failed\n"); 246 | return ; 247 | } 248 | else if(pid == 0) // 子进程 249 | { 250 | signal(SIGINT, SIG_IGN); 251 | signal(SIGQUIT, SIG_IGN); 252 | signal(SIGTSTP,SIG_IGN); 253 | execvp(arg[0], arg); 254 | } 255 | else // 父进程 256 | { 257 | signal(SIGCHLD, bg_signal_handle); 258 | signal(SIGINT, sig_handle); // SIGINT = 2, 用户键入Ctrl-C 259 | signal(SIGQUIT, sig_handle); /* SIGQUIT = 3 用户键入Ctr+\ */ 260 | signal(SIGTSTP, sig_handle); // SIGTSTP =20,一般有Ctrl-Z产生 261 | bg_struct_handle(pid, arg, 0); 262 | dup2(current_out, 1); 263 | dup2(current_in, 0); 264 | return ; 265 | } 266 | 267 | } 268 | } 269 | ``` 270 | # 四、实验总结 271 | 通过本实验,我们完成了一个支持输入输出重定向,支持后台运行的 Linux Shell 命令解释器。通过该项目的学习,可以更进一步的了解 Linux Shell 的工作机制。在使用重定向 > 和>> 时 建议将目录切换到自己的家目录,否则没权限建立重定向文件。 272 | 但是在本次实验中出现了一点bug还没有解决,重定`< file1`和`< file1 >> file2` 的功能在运行时没有效果。希望各位童鞋能够改进代码,代码中的不足也希望大家能够指出,谢谢。 273 | - 该项目的完整代码见:github 274 | 275 | # 五、参考资料 276 | 277 | - [实验楼](https://www.shiyanlou.com/) 278 | - [UNIX环境高级编程》](https://book.douban.com/subject/1788421/) 279 | -------------------------------------------------------------------------------- /shell.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #define MAX 1024 15 | #define MAX_COMM 100 16 | 17 | // 全局声明 18 | void print_prompt(); 19 | void bg_struct_handle(pid_t pid, char *arg[], int type); 20 | char cwd[MAX]; //保存当前路径 21 | char *all[MAX]; //all 用来保存命令行字符串 22 | int current_out = 4; 23 | int current_in = 5; 24 | int fd[4]; 25 | 26 | typedef struct proc{ //针对后台进程 27 | pid_t pid; 28 | int status; 29 | char *arg[MAX_COMM]; 30 | struct proc *next; 31 | }proc; 32 | proc *start=NULL; 33 | 34 | 35 | /* 信号处理函数 */ 36 | void sig_handle(int sig) 37 | { 38 | if(sig == SIGINT) // SIGINT = 2 39 | { 40 | printf("\nInstead of Ctrl-C type quit\n"); 41 | print_prompt(); 42 | } 43 | else if(sig == SIGQUIT) //SIGQUIT = 3 44 | { 45 | printf("\nType quit to exit\n"); 46 | print_prompt(); 47 | } 48 | else if(sig == SIGTSTP) 49 | { 50 | printf("\nInstead of Ctrl-Z type pause\n"); 51 | print_prompt(); 52 | } 53 | } 54 | /* 用于输出提示符 */ 55 | void print_prompt() 56 | { 57 | // 调用uname获取系统信息 58 | struct utsname uname_ptr; 59 | struct passwd *pid_to_name; 60 | uname(&uname_ptr); 61 | pid_to_name = getpwuid(getuid()); 62 | //调用 getcwd 获取当前路径名,并存储在 cwd 指向的字符串 63 | getcwd(cwd, sizeof(cwd)); 64 | setbuf(stdout, NULL); //禁用 buffer, 直接将 printf 要输出的内容输出 65 | printf("<%s@%s:%s> ",pid_to_name->pw_name,uname_ptr.sysname, cwd); 66 | } 67 | 68 | /* 扫描用户输入的命令行 */ 69 | void scan_command(char *command) 70 | { 71 | int bytes_read; 72 | size_t nbytes = MAX; 73 | bytes_read = getline(&command, &nbytes, stdin); /*从标准输入中读入包含 74 | 多条命令行语句的字符串,并保存在 command变量中 */ 75 | bytes_read -= 1; 76 | command[bytes_read] = '\0'; 77 | } 78 | 79 | /* 以空格为分解命令及其参数 */ 80 | void *parse(char *command, int time) 81 | { 82 | char *comm; // 用于储存命令或命令的参数 83 | if(time ==0) 84 | comm = strtok(command, " "); 85 | else 86 | comm = strtok(NULL, " "); 87 | return comm; 88 | } 89 | 90 | /* 分割用户以分号分隔的多命令,如“ls;cd”*/ 91 | void parse_semicolon(char *command) 92 | { 93 | int i ; 94 | for (i=0; i < MAX; i++) 95 | all[i] = (char *) malloc(MAX_COMM * sizeof(char)); 96 | i = 0; 97 | all[i] = strtok(command, ";"); //注意 strtok 的用法,用来分割命令行 98 | while(1) 99 | { 100 | i += 1; 101 | all[i] = strtok(NULL, ";"); 102 | if(all[i] == NULL) 103 | break; 104 | } 105 | } 106 | 107 | /* 内建命令 cd 的实现 */ 108 | void cd(char *arg) 109 | { 110 | if(arg == NULL) 111 | { 112 | printf("insufficient arguments\n"); 113 | } 114 | else 115 | { 116 | int cond; 117 | cond = chdir(arg); 118 | if(cond == -1) 119 | { 120 | printf("wrong path\n"); 121 | } 122 | 123 | } 124 | } 125 | 126 | /* 调用 bg_struct_handle 对后台进程 proc 信息的处理 */ 127 | void bg_signal_handle() 128 | { 129 | int status; 130 | pid_t pid; 131 | pid = waitpid(-1, &status, WNOHANG); 132 | proc *iterate; 133 | iterate = start; 134 | if(iterate != NULL&&pid>0) 135 | { 136 | bg_struct_handle(pid, NULL, 1); // 将结束的子进程从 proc 上删除相关信息(第三个参数为1) 137 | } 138 | } 139 | 140 | /* 处理 proc 链表(增、删、打印) */ 141 | void bg_struct_handle(pid_t pid, char *arg[], int type) 142 | { 143 | proc *iterate, *new; 144 | if(type == 0) // proc 链表上增加一个 proc 结构 145 | { 146 | if(start == NULL) 147 | { 148 | start = (proc *)malloc(sizeof(proc)); 149 | start -> pid = pid; 150 | start -> status = 1; 151 | start -> next = NULL; 152 | int i = 0; 153 | while(arg[i] != NULL) 154 | { 155 | start -> arg[i] = malloc(MAX_COMM * sizeof(char)); 156 | strcpy(start -> arg[i], arg[i]); 157 | i += 1; 158 | } 159 | start -> arg[i] = NULL; 160 | } 161 | else 162 | { 163 | new = (proc *)malloc(sizeof(proc)); 164 | new -> pid = pid; 165 | new -> status = 1; 166 | new -> next = NULL; 167 | int i = 0; 168 | while(arg[i] != NULL) 169 | { 170 | new -> arg[i] = malloc(MAX_COMM * sizeof(char)); 171 | strcpy(new -> arg[i], arg[i]); 172 | i += 1; 173 | } 174 | new -> arg[i] = NULL; 175 | iterate = start ; 176 | while(iterate -> next != NULL) 177 | iterate = iterate -> next; 178 | iterate -> next = new; 179 | } 180 | } 181 | else if(type == 1) // proc 链表上删除一个 proc 结构 182 | { 183 | proc *preiter = NULL; 184 | iterate = start; 185 | while(iterate != NULL && iterate -> pid != pid ) 186 | { 187 | preiter = iterate; 188 | iterate = iterate -> next; 189 | } 190 | if(iterate == NULL) 191 | { 192 | printf("No Such Pid !\n"); 193 | return ; 194 | } 195 | else if(iterate -> pid == pid) 196 | { 197 | if(preiter == NULL) 198 | { 199 | start = iterate -> next; 200 | free(iterate); 201 | } 202 | else 203 | { 204 | preiter -> next = iterate -> next; 205 | free(iterate); 206 | } 207 | } 208 | } 209 | else if(type == 2) //迭代地打印 proc 链表的 proc 结构信息 210 | { 211 | int i = 1, a = 0; 212 | iterate = start; 213 | if (iterate == NULL) 214 | { 215 | printf("No Background jobs\n"); 216 | return ; 217 | } 218 | while(iterate != NULL) 219 | { 220 | a = 0; 221 | setbuf(stdout, NULL); 222 | printf("[%d] ",i); 223 | while(iterate -> arg[a] != NULL) 224 | { 225 | printf("%s ", iterate -> arg[a]); 226 | a += 1; 227 | } 228 | printf("[%d]\n", iterate -> pid); 229 | i += 1; 230 | iterate = iterate -> next; 231 | } 232 | } 233 | return ; 234 | } 235 | 236 | /* 创建子进程,调用 execvp 函数执行命令程序(前后台)*/ 237 | void bf_exec(char *arg[], int type) 238 | { 239 | pid_t pid; 240 | if(type == 0) // 前台执行 241 | { 242 | if((pid = fork()) < 0) 243 | { 244 | printf("*** ERROR: forking child process failed\n"); 245 | return ; 246 | } 247 | // 父子进程执行代码的分离 248 | else if(pid == 0) //子进程 249 | { 250 | signal(SIGTSTP, SIG_IGN); 251 | signal(SIGINT,SIG_DFL);//用于杀死子进程 252 | signal(SIGQUIT, SIG_IGN); 253 | execvp(arg[0], arg); // execvp 用于在子进程中替换为另一段程序 254 | } 255 | else //父进程 256 | { 257 | signal(SIGCHLD, bg_signal_handle); 258 | signal(SIGINT, sig_handle); // SIGINT = 2, 用户键入Ctrl-C 259 | signal(SIGQUIT, sig_handle); /* SIGQUIT = 3 用户键入Ctr+\ */ 260 | signal(SIGTSTP, sig_handle); // SIGTSTP =20,一般有Ctrl-Z产生 261 | pid_t c; 262 | c = wait(&pid); //等待子进程结束 263 | dup2(current_out, 1); //还原默认的标准输出 264 | dup2(current_in, 0); //还原默认的标准输入 265 | return; 266 | } 267 | } 268 | else // 后台执行 269 | { 270 | if((pid = fork()) < 0) 271 | { 272 | printf("*** ERROR: forking child process failed\n"); 273 | return ; 274 | } 275 | else if(pid == 0) // 子进程 276 | { 277 | signal(SIGINT, SIG_IGN); 278 | signal(SIGQUIT, SIG_IGN); 279 | signal(SIGTSTP,SIG_IGN); 280 | execvp(arg[0], arg); 281 | } 282 | else // 父进程 283 | { 284 | signal(SIGCHLD, bg_signal_handle); 285 | signal(SIGINT, sig_handle); // SIGINT = 2, 用户键入Ctrl-C 286 | signal(SIGQUIT, sig_handle); /* SIGQUIT = 3 用户键入Ctr+\ */ 287 | signal(SIGTSTP, sig_handle); // SIGTSTP =20,一般有Ctrl-Z产生 288 | bg_struct_handle(pid, arg, 0); 289 | dup2(current_out, 1); 290 | dup2(current_in, 0); 291 | return ; 292 | } 293 | 294 | } 295 | } 296 | 297 | /* 处理输出重定向文件的问题 */ 298 | void file_out(char *arg[], char *out_file, int type) 299 | { 300 | int f; 301 | current_out = dup(1); 302 | if(type == 0) //处理以覆盖的方式重定向“>” 303 | { 304 | f = open(out_file, O_WRONLY | O_CREAT, 0777); 305 | dup2(f, 1); //复制文件描述符f,并指定为 1,也就是让标准输出重定向到指定的文件 306 | close(f); 307 | bf_exec(arg, 0); 308 | } 309 | else // 处理以追加的方式重定向“>>” 310 | { 311 | f = open(out_file, O_WRONLY | O_CREAT | O_APPEND , 0777); //以 O_APPEND 模式打开 312 | dup2(f, 1); 313 | close(f); 314 | bf_exec(arg, 0); 315 | } 316 | } 317 | 318 | /* 处理输入重定向文件的问题 */ 319 | void file_in(char *arg[], char *in_file, char *out_file, int type) 320 | { 321 | int in; 322 | in = open(in_file, O_RDONLY); 323 | current_in = dup(0); // 324 | dup2(in, 0); 325 | close(in); 326 | if(type == 0) // 单一的输入重定向 327 | { 328 | printf("Going to execute bf_exec\n"); //debug remove it 329 | bf_exec(arg, 0); 330 | } 331 | else if(type == 1) // 双重重定向 `< ... >` 332 | { 333 | file_out(arg, out_file, 0); 334 | } 335 | else // 双重重定向 `< ... >>` 336 | { 337 | file_out(arg, out_file, 1); 338 | } 339 | return; 340 | } 341 | /* 执行单条命令行语句 */ 342 | void execute(char *command) 343 | { 344 | char *arg[MAX_COMM]; 345 | char *try; 346 | arg[0] = parse(command, 0); //获得命令名称的字符串指针,如“ls” 347 | int t = 1; 348 | arg[t] = NULL; 349 | if (strcmp(arg[0], "exit") == 0) // 为了方便用户退出shell 350 | { 351 | puts("试图退出解析器···"); 352 | exit(0); 353 | } 354 | if (strcmp(arg[0], "cd") == 0) // 处理内嵌命令“cd”的情况 355 | { 356 | try = parse(command, 1); 357 | cd(try); 358 | return ; 359 | } 360 | if(strcmp(arg[0],"lsbg")) 361 | { 362 | // 363 | } 364 | 365 | // 循环检测剩下的命令参数,即检测是否:重定向?管道?后台执行?普通命令参数? 366 | while (1) 367 | { 368 | try = parse(command, 1); 369 | if (try == NULL) 370 | break; 371 | 372 | else if (strcmp(try, ">") == 0) // 重定向到一个文件的情况 373 | { 374 | try = parse(command, 1); // 得到输出文件名 375 | file_out(arg, try, 0); // 参数 0 代表覆盖的方式重定向 376 | return; 377 | } 378 | 379 | else if (strcmp(try, ">>") == 0) // 追加重定向到一个文件 380 | { 381 | try = parse(command, 1); 382 | file_out(arg, try, 1); // 参数 1 代表追加的方式重定向 383 | return; 384 | } 385 | 386 | else if (strcmp(try, "<") == 0) // 标准输入重定向 387 | { 388 | try = parse(command, 1); // 输入重定向的输入文件 389 | char *out_file = parse(command, 1); // 输出重定向的输出文件 390 | if (out_file != NULL) 391 | { 392 | if (strcmp(out_file, ">") == 0) // 输入输出文件给定 393 | { 394 | out_file = parse(command, 1); 395 | if (out_file == NULL) 396 | { 397 | printf("Syntax error !!\n"); 398 | return; 399 | } 400 | else 401 | file_in(arg, try, out_file, 1); // 参数 1 针对双重定向 < > 402 | } 403 | else if (strcmp(out_file, ">>") == 0) 404 | { 405 | out_file = parse(command, 1); 406 | if (out_file == NULL) 407 | { 408 | printf("Syntax error !!\n"); 409 | return; 410 | } 411 | else 412 | file_in(arg, try, out_file, 2); // 参数 2 针对双重定向 < >> 413 | } 414 | } 415 | else 416 | { 417 | file_in(arg, try, NULL, 0); // 模式 0 针对单一的输入重定向 418 | } 419 | } 420 | //处理后台进程 421 | else if (strcmp(try, "&") == 0) // 后台进程 422 | { 423 | bf_exec(arg, 1); // bf_exec 的第二个参数为 1 代表后台进程 424 | return; 425 | } 426 | else //try是一个命令参数 427 | { 428 | arg[t] = try; 429 | t += 1; 430 | arg[t] = NULL; 431 | } 432 | } 433 | 434 | bf_exec(arg, 0); // 参数 0 表示前台运行 435 | return; 436 | } 437 | void signal_set(){ 438 | /* 捕捉信号,当信号发生时调用信号处理函数sig_handle,建议用sigaction改写 */ 439 | signal(SIGINT, sig_handle); // SIGINT = 2, 用户键入Ctrl-C 440 | signal(SIGQUIT, sig_handle); /* SIGQUIT = 3 用户键入Ctr+\ */ 441 | signal(SIGCHLD, bg_signal_handle); //SIGCHLD = 17,一般在子进程中止时产生 442 | signal(SIGTSTP, sig_handle); // SIGTSTP =20,一般有Ctrl-Z产生 443 | return; 444 | } 445 | /* 主函数入口 */ 446 | int main() 447 | { 448 | char *command; 449 | int iter = 0; 450 | command = (char *)malloc(MAX+1); //用于存储命令语句 451 | 452 | chdir("/home/"); /* 通常在登陆 shell 的时候通过查找配置文件(/etc/passwd 文件) 453 | 中的起始目录,初始化工作目录,这里默认为家目录 */ 454 | while(1) 455 | { 456 | iter = 0; 457 | //定义信号处理方式 458 | signal_set(); 459 | //用于输出提示符 460 | print_prompt(); 461 | 462 | //扫描多条命令语句 463 | scan_command(command); 464 | 465 | // 基于分号解析出单条命令语句,并以字符串的形式存进全局变量 all 中 466 | parse_semicolon(command); 467 | 468 | // 迭代执行单条命令语句 469 | while(all[iter] != NULL) 470 | { 471 | execute(all[iter]); //这是 shell 解释器的核心函数 472 | iter += 1; 473 | } 474 | } 475 | } 476 | --------------------------------------------------------------------------------