实现不带 .b
后缀指令 在 int spawn(char *prog, char **argv)
中对 prog 特判,检查是否为 .b
结尾,如果不是则增加后缀。
1 2 3 4 5 6 7 8 char prog_with_dotb[1024 ];strcpy (prog_with_dotb, prog);int len = strlen (prog_with_dotb);if (!(prog_with_dotb[len-1 ]=='b' && prog_with_dotb[len-2 ]=='.' )) { prog_with_dotb[len]='.' ; prog_with_dotb[len+1 ]='b' ; prog_with_dotb[len+2 ]=0 ; }
实现指令条件执行 获取进程返回值 在 void libmain(int argc, char **argv)
中获取 main 的返回值并通过 ipc 发送给父进程(runcmd 函数进程),然后再通过 ipc 从父进程发送给 shell 主进程(sh.c 的 main 函数进程),从而获取单条命令的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void libmain (int argc, char **argv) { env = &envs[ENVX(syscall_getenvid())]; int res = main(argc, argv); ipc_send(env->env_parent_id, res, 0 , 0 ); exit (); } int runcmd (char *s) { int child = spawn(argv[0 ], argv); if (child >= 0 ) { res = ipc_recv(0 , 0 , 0 ); wait(child); } }
实现条件控制 首先在读入命令的时候进行预处理,将 ||
和 &&
用特殊字符标记。
1 2 3 4 5 for (int i=0 ;buf[i];i++) if (buf[i]=='|' && buf[i+1 ]=='|' ) (buf[i]=1 ), buf[i+1 ]=' ' ; else if (buf[i] == '&' && buf[i+1 ]=='&' ) (buf[i]=2 ), buf[i+1 ]=' ' ;
然后扫描字符串,显然字符串被每个特殊字符分隔成了多个命令,单独执行并获取返回值,然后决定是否执行下一条命令即可。注意末尾的最后一条命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 for (int i=0 , j=0 ;i<=len;i++) { if (buf[i]==1 || buf[i]==2 || i==len || buf[i]==3 ) { char command[1024 ]; int k=0 ; for (;j<i;j++) command[k++]=buf[j]; command[k]=0 ; j++; if ((r = fork()) < 0 ) { user_panic("fork: %d" , r); } if (r == 0 ) { ipc_send(env->env_parent_id, runcmd(command), 0 , 0 ); exit (); } else { res = ipc_recv(0 , 0 , 0 ); wait(r); } if (res == 0 ) { if (buf[i] == 1 ) { while (buf[i] != 2 && buf[i]) i++; if (i==len) { break ; } j=i+1 ; } } else if (buf[i] == 2 ) { break ; } } }
实现更多指令 touch 指令 仿照 sh.c 的 parsecmd 函数中对于 >
的处理即可。
sh.c 中的代码如下,注意课程组的原始实现是错误的,需要在 open 函数(第 6 行)的参数里加上 O_TRUNC。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 case '>' : { if (gettoken(0 , &t) != 'w' ) { debugf("syntax error: > not followed by word\n" ); exit (); } if ((fd = open(t, O_WRONLY | O_CREAT | O_TRUNC)) < 0 ) { debugf("err when open %s for write: %d\n" , t, fd); exit (); } if ((r = dup(fd, 1 )) < 0 ) { debugf("err when dup %d: %d\n" , fd, r); exit (); } if ((r = close(fd)) < 0 ) { debugf("err when close %d: %d\n" , fd, r); exit (); } break ; }
那么 touch.c 就很简单了。
首先通过简单的字符串处理找到文件所在的目录
1 2 3 4 5 for (;i>=0 ;i--) if (path[i]=='/' ) break ; for (int j=0 ;j<=i;j++) dir[j]=path[j];
用 Stat 结构体检查目录状态,注意判断目录是否为 dir 类型
1 2 3 4 5 6 7 8 struct Stat st ;if ((r = stat(dir, &st)) < 0 ) { printf ("touch: cannot touch '%s': No such file or directory\n" , path); return ; } else if (!st.st_isdir) { printf ("touch: cannot touch '%s': No such file or directory\n" , path); return ; }
然后检查文件是否存在
1 2 3 if ((r = stat(path, &st)) == 0 ) { return ; }
如果一切正常,那么新建文件,复制 sh.c 的代码即可。
1 2 3 4 5 6 7 8 9 10 11 12 if ((fd = open(path, O_WRONLY | O_CREAT)) < 0 ) { debugf("err when open %s for write: %d\n" , path, fd); return ; } if ((r = dup(fd, 1 )) < 0 ) { debugf("err when dup %d: %d\n" , fd, r); return ; } if ((r = close(fd)) < 0 ) { debugf("err when close %d: %d\n" , fd, r); return ; }
mkdir 指令
参数的处理仿照 ls.c 即可:
1 2 3 4 5 6 7 8 ARGBEGIN { default : usage(); case 'p' : flag[(u_char)ARGC()]++; break ; } ARGEND
对于父目录和目录状态的检查同 touch 指令,不再赘述。注意检查是否有 -p
选项,如果有则递归创建。
1 2 3 4 5 6 7 8 9 10 r = stat(dir, &st); if (r != 0 && !flag['p' ]) { printf ("mkdir: cannot create directory '%s': No such file or directory\n" , path); return ; } else if (r == 0 ) { } else { mkdir(dir); mkdir(path); }
创建目录的方法和创建文件的方法相同,加入参数 O_MKDIR 即可。
1 2 3 4 if ((fd = open(path, O_WRONLY | O_CREAT | O_MKDIR | O_TRUNC)) < 0 ) { debugf("err when open %s for write: %d\n" , path, fd); return ; }
rm 指令 参数的处理仿照 ls.c。路径的检查和上文一样。最后调用 remove 函数即可。
1 2 3 4 5 6 7 8 9 10 if ((r = stat(path, &st)) != 0 ) { if (!flag['f' ]) printf ("rm: cannot remove '%s': No such file or directory" , path); return ; } if (st.st_isdir && !flag['r' ]) { printf ("rm: cannot remove '%s': Is a directory" , path); return ; } remove(path);
实现反引号 仿照管道符号 |
,用管道获取输出内容并复制到字符串里,然后作为参数回传即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 case '`' : { int p[2 ]; if ((r = pipe(p)) < 0 ) { } if ((*rightpipe = fork()) < 0 ) { } if (*rightpipe != 0 ) { for (int i=0 ;i<8192 ;i++) read(0 , echo_buf+i, 1 ); argv[argc++]=echo_buf; break ; } else { runcmd(t); return 0 ; } break ; }
实现注释功能 在 sh.c 的 SYMBOLS 里加入 #
号,并在 parsecmd 里进行对应的处理即可。
1 2 3 case '#' : { return argc; }
实现历史指令 打印历史指令 用一个数组保存所有指令,在 runcmd 里特判 history 指令即可。
1 2 3 4 5 6 7 8 9 if (strcmp (argv[0 ], "history" ) == 0 ) { for (int i = cnt_commands >= 20 ? cnt_commands - 20 : 0 ; i < cnt_commands; i++) printf ("%s\n" , commands[i]); close_all(); if (rightpipe) { wait(rightpipe); exit (); } return 0 ; }
实现光标移动 在 readline 函数里对读取的字符进行即时判断即可。详见下面这段代码。就是个前端工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 if (' ' <= c && c <= '~' ) { len++; for (int j = len; j > i; j--) buf[j] = buf[j-1 ]; buf[i] = c; } else if (c == 0x7f || c == '\b' ) { if (i == 0 ) { printf ("\b " ); } else { printf ("\b" ); len--; i--; for (int j = i; j < len; j++) { buf[j] = buf[j+1 ]; printf ("%c" , buf[j]); } printf (" \b" ); for (int j = i; j < len; j++) printf ("\b" ); } i--; } else if (c == 27 ) { for (int _ = 0 ; _ < 2 ; _++) if ((r = read(0 , &c, 1 )) != 1 ) { } switch (c) { case 'A' : { printf ("\033[B" ); if (now_id == 0 ) { ; } else { for (; i < len; i++) printf (" " ); for (int i = 0 ; i < len; i++) printf ("\b \b" ); buf[len] = 0 ; strcpy (commands[now_id], buf); now_id--; strcpy (buf, commands[now_id]); for (i = 0 ; i < len; i++) printf ("%c" , buf[i]); } i = len - 1 ; break ; } case 'B' : { for (; i < len; i++) printf (" " ); for (int i = 0 ; i < len; i++) printf ("\b \b" ); if (now_id == cnt_commands) { ; } else { now_id++; strcpy (buf, commands[now_id]); for (i = 0 ; i < len; i++) printf ("%c" , buf[i]); } i = len - 1 ; break ; } case 'C' : { if (i == len) { i--; printf ("\b" ); } break ; } case 'D' : { if (i == 0 ) { i--; printf (" " ); } else i-=2 ; break ; } } }
实现一行多指令 同条件控制,在读入命令的时候进行预处理,以 ;
为间隔分隔各个指令分别执行即可。
实现追加重定向 在 _gettoken 函数里对 >
和 >>
进行特判,如果是 >>
则返回 ~
。
在 parsecmd 里进行对应的处理:打开(新建)文件,不要清空文件(即 O_TRUNC 参数)把光标移动到文件末尾,其余和 >
的处理一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 case '~' : { if (gettoken(0 , &t) != 'w' ) { } if ((fd = open(t, O_WRONLY | O_CREAT)) < 0 ) { } struct Stat st ; stat(t,&st); seek(fd, st.st_size); if ((r = dup(fd, 1 )) < 0 ) { debugf("err when dup %d: %d\n" , fd, r); exit (); } if ((r = close(fd)) < 0 ) { debugf("err when close %d: %d\n" , fd, r); exit (); } break ; }
实现引号支持 在 _gettoken 函数里对双引号特判,将双引号之间的内容作为一个字段(即 w
)回传即可。
1 2 3 4 5 6 7 8 if (*s == '"' ) { s++; *p1 = s; while (*s++ != '"' ); *(s-1 )=0 ; *p2 = s; return 'w' ; }
实现前后台任务管理 与历史指令的实现相同。在 runcmd 里特判输入即可。
jobs 指令需要获取对应进程状态,可以通过判断 env_status 实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 char *get_status (int envid) { struct Env *e = envs + ENVX(envid); if ((e->env_status == ENV_FREE) || e->env_id != envid) { return DONE; } else { return RUNNING; } } if (strcmp (argv[0 ], "jobs" ) == 0 ) { for (int i=1 ;i<=cnt_bg;i++) { printf ("[%d] %-10s 0x%08x %s\n" , i, get_status(ids[i]), ids[i], cmds[i]); } close_all(); if (rightpipe) { wait(rightpipe); exit (); } return 0 ; }
fg 指令的效果和 wait 相同。
1 2 3 4 5 6 7 8 9 if (strcmp (argv[0 ], "fg" ) == 0 ) { if (jobid == 0 || jobid > cnt_bg) { } else if (get_status(ids[jobid]) != RUNNING) { } wait(ids[jobid]); close_all(); return 0 ; }
其中 kill 指令需要魔改 syscall_env_destroy 以便让非父进程也可以杀掉对应的进程。具体来说是在 sys_env_destroy 中关闭 envid2env 的 checkperm 选项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int sys_env_destroy (u_int envid) { struct Env *e ; try(envid2env(envid, &e, 0 )); printk("[%08x] destroying %08x\n" , curenv->env_id, e->env_id); env_destroy(e); return 0 ; } if (strcmp (argv[0 ], "kill" ) == 0 ) { if (jobid == 0 || jobid > cnt_bg) { } else if (get_status(ids[jobid]) != RUNNING) { } syscall_env_destroy(ids[jobid]); close_all(); return 0 ; }