Linux C 开发实战—myshell
时间过的飞快,不知不觉中离笔者写完myshell
已经过了不少时间了.为了进一步的巩固笔者当初从开发实战中学习到的知识,笔者决定还是补上这篇拖延了很久的博客.
需求
- 支持使用任意数量的
管道
- 支持使用命令调用其他程序
- 支持使用任意数量的
重定向输入输出
- 内置
cd
命令 - 内置
history
命令 - 支持
Tab键
补全 - 实现光标移动
- 屏蔽相关信号,防止
Ctrl+C
杀死 - 界面美观
开发过程
头文件
1 2 3 4 5 6 7 8 9 10
| #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> #include <readline/history.h> #include <readline/readline.h>
|
宏和全局变量
1 2 3 4 5 6 7 8
| extern char **environ; struct COMMAND { int argc; int Redirect_FD[3]; char **argv; }; char *oldpath;
|
错误处理
1 2 3 4 5 6
| void myerror(char *string, int line) { fprintf(stderr, "\aLine:%d,error:\a\n", line); fprintf(stderr, "%s:%s\n", string, strerror(errno)); exit(EXIT_FAILURE); }
|
开发前的分析
多重管道
可以使用 分治
的思想逐层处理,化简为单重管道的情况,而单重管道可视为先后发生A >./tmpfile
和 B <./tmpfile
的情况,因此管道和重定向符的实现紧密相关.- 重定向符有很多种格式,例如:
>
、 >>
、 1>
、 1>>
、 2>
、 2>>
、<
、 <<
、 1>&2
、 1>>&2
、 2>&1
、 2>>&1
,但这次练习的重点不是字符串的解析,故此笔者不计划实现最后的五种. Tab
键 补全、历史记录的存放等功能均由 readline
库实现(感谢GNU Project
为此作出的贡献).- 界面美观的要求通过输出带有颜色的文字和输出对齐的文本来实现
- 调用其他程序则涉及进程控制的相关内容
获取并解析用户输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int main(void) { read_history(NULL);
while (1) { char *command = readline("MYSHELL$"); add_history(command); write_history(NULL); launch(command); free(command); }
if (oldpath != NULL) { free(oldpath); } }
|
通过 readline
库提供的 readline()
函数,便可轻松的输出 命令提示符
并获取用户输入.
将命令拆分成多段
此时,笔者运用著名的 分治
思想,将形如 A -b cde -f | g -hi | j -k lmn >123.txt
的命令以 |
为界线拆成多段,分别处理.
笔者在前文分析过 管道
可以用两个输入输出重定向来实现.
下面分析实现的具体方法,取一条以|
符号为界分为 $n$段的命令( $\forall n \in\mathbb N^+, n \geq 3$ )
- 考察该命令的第 $1$ 段.管道要求第二段命令的输入为第一段命令的输出.因此可将第 $1$ 段命令的标准输出重定向至临时文件,并将第 $2$ 段命令的标准输入重定向至该临时文件.
- 考察该命令的第 $i$ 段( $\forall i \in\mathbb N, 1 < i < n $).该段命令的输入为第 $i-1$ 段的输出,可将第 $i-1$ 段的标准输出重定向至临时文件,并将第 $i$ 段的标准输入重定向至该临时文件;该段命令的输出为第 $i+1$ 段的输入,可将第 $i$ 段的标准输出重定向至临时文件,并将第 $i+1$ 段的标准输入重定向至该临时文件
- 考察该命令的第 $n$ 段.该段的输入为第 $n-1$ 段的输出.因此可将第 $n-1$ 段的标准输出重定向至临时文件,并将第 $n$ 段的标准输入重定向至该临时文件.
这就是实现管道的全部流程.
但问题来了,如何处理形如 A -b cde -f >./log.txt | g -hi | j -k lmn <123.txt
的命令?在上段中,笔者分析了第 $1$ 段的标准输入要重定向至临时文件,但命令中却要求重定向至 log.txt
.
笔者曾考虑复制一份重定向中产生的临时文件至 ./log.txt
或者用 log.txt
代替临时文件的功能,这样就能上例中的冲突.但是请思考这个例子 ls -al >/dev/null |wc -c
.
这个命令中wc -c
命令读的结果根据实现方法会有不同.在笔者的环境中使用 zsh
执行该命令的结果不为 0
,但使用 GNU bash
执行该命令的结果为 0
.笔者认为类似上面的命令具有 二义性
,故此笔者的 myshell
实现中对形如 A -b cde -f >./log.txt | g -hi | j -k lmn >123.txt
、ls -al >/dev/null |wc -c
、ls -alR / |grep test <./result.md
这类命令做报错处理,欢迎读者们在评论区留言和笔者讨论这个问题.
好了,至此笔者说明了本程序的绝大部分设定和思想,下面就可以来讨论 launch()
函数的具体实现了.
首先遍历一遍命令,计算命令中的管道数量.
1 2 3 4 5 6 7 8
| int pipe = 0; for (char *pr = command; *pr != '\0'; pr++) { if (*pr == '|') { pipe++; } }
|
计算出了管道的数量也就知道了命令需要被分成几段.那么就可以根据分段的数量创建一个 COMMAND
的数组.
1 2 3 4 5
| struct COMMAND *cmd = (struct COMMAND *)calloc(pipe + 1, sizeof(struct COMMAND)); if (cmd == NULL) { myerror("malloc", __LINE__); }
|
然后就是将命令分段的实现了.
1 2 3 4 5 6 7 8 9
| char *remain = NULL; char *part = strtok_r(command, "|", &remain); for (int i = 0; i <= pipe; i++) { cmd[i].Redirect_FD[STDIN_FILENO] = -1; cmd[i].Redirect_FD[STDOUT_FILENO] = -1; cmd[i].Redirect_FD[STDERR_FILENO] = -1; }
|
还记得吗?笔者用 Redirect_FD
表示每段命令中重定向的文件的文件描述符.因为合法的文件描述符
都是非负
的,那么笔者必须要将 Redirect_FD
中的每个元素都初始化为 -1
才能表达不需要重定向的情况.
tip
TIP
笔者猜会有读者对strtok_r()
函数的使用产生疑惑.strtok_r()
函数的用法与strtok()
函数的用法类似,只是多了一个参数.
这两个函数的函数原型为:
char strtok(char str, const char delim);
char strtok_r(char str, const char delim, char **saveptr);
简单的说,strtok_r()
是可重入版本的 strtok()
,就是将使用 static
变量保存的数据保存在了参数里,实现了可重入的需求.
至于为什么要用 strtok_r()
而不是 strtok()
?因为后文还有一处有分割字符串的需求,如果都用 strtok()
来实现,那么在第2处调用(指第一个参数不为NULL
的第2处调用)会覆盖先前保存在 static
变量中的数据,无法满足笔者的需求.后文会再次重复该问题.
在这之后,分别处理每段命令.
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
| for (int i = 0; i <= pipe; i++) {
if (pipe && i < pipe) { char TempFile[] = "/tmp/MyShell_XXXXXX"; int TempFile_FD = mkstemp(TempFile); if (TempFile_FD == -1) { myerror("mkstemp", __LINE__); } cmd[i].Redirect_FD[STDOUT_FILENO] = TempFile_FD; cmd[i + 1].Redirect_FD[STDIN_FILENO] = TempFile_FD; unlink(TempFile); } analyze(part, &cmd[i]); if (不是内置命令) { 执行本段命令 } if (pipe && i < pipe) { lseek(cmd[i].Redirect_FD[STDOUT_FILENO], 0, SEEK_SET); cmd[i].Redirect_FD[STDOUT_FILENO] = -1; } part = strtok_r(NULL, "|", &remain); for (int IO_Steam = 0; IO_Steam < 3; IO_Steam++) { if (cmd[i].Redirect_FD[IO_Steam] >= 0) { close(cmd[i].Redirect_FD[IO_Steam]); } }
for (int j = 0; j < cmd[i].argc; j++) { free(cmd[i].argv[j]); } free(cmd[i].argv); } free(cmd); }
|
为了便于读者们阅读和理解,第22
行和第23
行笔者使用了伪码来描述其中的逻辑.具体的实现将在后文说明.
请读者们注意第28
行,该行将文件的读取位置重置为0.以便下一段命令从文件头读取内容.
第29
行,在本段命令执行结束后,将因实现管道产生的重定向中的输出重定向设为-1
.为什么要这样做?为了避免 close
临时文件,在第18
行已经对临时文件执行了unlink
,close
后临时文件的引用计数递减为0,会导致临时文件被真正的删除,下一段命令将无法完成输入重定向.故此,临时文件只能在完成输入重定向的使命之后关闭.
最终,所有打开的重定向文件都该被将被close
.
分析处理命令段
首先将正在处理的命令段复制一份,因为在分析中会更改命令段的值.
1 2 3 4 5
| char *string = strdup(OriginString); if (string == NULL) { myerror("malloc", __LINE__); }
|
tip
TIP
strdup()
的用法等于用strlen()
计算源字符串的长度后分配为新字符串分配内存空间并完成复制最终返回原字符串的副本的地址.
srdup()
的函数签名为:
char *strdup(const char *s);
在这之后定义变量char *end = string + strlen(string);
作为一个哨兵
指向\0
,标记string
的结束位置,防止指针越界.
下面就是查找命令段中是否含有输入输出重定向,重定向是否合法,以及解析命令行的参数,将其转换为char **argv;
的形式.
处理标准输出、错误输出重定向
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
| char *result = NULL; while ((result = strchr(string, '>')) != NULL) { *result = ' '; int IO_Steam = 1;
result--; if (result > string && isdigit(*result)) { if (*result - '0' != STDOUT_FILENO && *result - '0' != STDERR_FILENO) { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } else { IO_Steam = *result - '0'; *result = ' '; } } if (cmd->Redirect_FD[IO_Steam] >= 0) { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } result += 2; _Bool Append = 0; if (result < end && *result == '>') { Append = 1; *result = ' '; } while (result < end && isspace(*result)) { result++; }
if (result < end) { cmd->Redirect_FD[IO_Steam] = OpenFile(result, O_WRONLY | O_CREAT | (Append ? O_APPEND : O_TRUNC)); } else { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } }
|
处理输入重定向
有了标准输出、错误输出重定向的处理方式,那标准输入重定向的处理方式也不会很难.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| while ((result = strchr(string, '<')) != NULL) { *result = ' '; if (cmd->Redirect_FD[STDIN_FILENO] >= 0) { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } while (result < end && isspace(*result)) { result++; } if (result < end) { cmd->Redirect_FD[STDIN_FILENO] = OpenFile(result, O_RDONLY); } else { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } }
|
解析命令行参数
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
| int arg_max = 16;
cmd->argv = (char **)calloc(arg_max, sizeof(char *)); if (cmd->argv == NULL) { myerror("malloc", __LINE__); } char *remain = NULL; result = strtok_r(string, " ", &remain); while (result != NULL) { if (arg_max < cmd->argc) { arg_max *= 2; cmd->argv = (char **)realloc(cmd->argv, arg_max * sizeof(char *)); } cmd->argv[cmd->argc++] = strdup(result); if (cmd->argv == NULL) { myerror("malloc", __LINE__); } result = strtok_r(NULL, " ", &remain); } if (arg_max < cmd->argc) { arg_max++; cmd->argv = (char **)realloc(cmd->argv, arg_max * sizeof(char *)); if (cmd->argv == NULL) { myerror("malloc", __LINE__); } } cmd->argv[cmd->argc] = NULL;
|
好了,这段命令的解析终于是结束了.当然还有一点小小的工作需要完成.free(string);
释放命令段的副本所占用的内存.
打开重定文件
临时文件的打开笔者在上文中已经实现完成.但用户在命令行中指定的重定向文件的打开还需要单独实现.
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
| int OpenFile(char *string, int flags) {
size_t len = 0; char *pr = string; while (!isspace(*pr) && *pr++ != '\0') { len++; }
char *dest = malloc((len + 1) * sizeof(char)); if (dest == NULL) { myerror("malloc failed", __LINE__); } strncpy(dest, string, len); dest[len] = '\0'; memset(string, ' ', sizeof(char) * len); PathAnalyze(&dest); int fd = open(dest, flags, S_IRUSR | S_IWUSR); if (fd == -1) { printf("error:fd:%d path:%s\n", fd, string); myerror("open", __LINE__); } free(dest); return fd; }
|
传入的string
是命令段的副本,这意味着重定向文件的路径后面可能还有以空格分隔的其他参数,这意味这不能直接使用string
调用open()
.
此处,笔者通过计算空格前的字符数量并将其复制到新的字符串中使字符串中只含有重定向文件的路径.
转换相对路径
遗憾的是,至此依然不能把dest
字符串直接当作参数去调用open()
.莫着急,请听笔者慢慢道来.
在此时,string
是重定向文件的路径是毫无疑问的.但路径并不都是可被open()
直接使用的.请参考笔者的前作(命令行参数的误区),文中说明了函数接受的路径只能是绝对路径
或以.
开头的相对路径.但用户输入的路径却不总是符合这里的要求.而将其他的相对路径格式转换为绝对路径是shell
的任务.
思考需要转换的两种相对路径格式.
~/123.md
该类相对路径只需要读取HOME
环境变量然后通过简单的字符串拼接就可完成转换.~root/123.md
该类相对路径的处理更加简单,直接完成拼接即可完成转换.
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
| void PathAnalyze(char **path) { char *RelativePath = *path; if (isalpha(*(RelativePath + 1))) { *path = malloc(strlen(RelativePath) + 1 + strlen("/home/") + 1); if (*path == NULL) { myerror("malloc", __LINE__); } strcpy(*path, "/home/"); } else { char *home = getenv("HOME"); *path = malloc(strlen(RelativePath) + 1 + strlen(home) + 1); if (*path == NULL) { myerror("malloc", __LINE__); } strcpy(*path, home); } strcat(*path, RelativePath + 1); free(RelativePath); }
|
至此,只需要根据传入的参数直接调用open()
函数便可完成打开.
执行命令段
有了刚才的准备工作,现在是万事俱备
了,只需要真正的执行命令段中的命令.
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
| void execute(struct COMMAND *cmd) {
pid_t pid = fork(); if (pid > 0) { wait(NULL); return; } for (int i = 1; i < cmd->argc; i++) { #ifndef NDEBUG printf("DEBUG,pid: %d LINE:%d\n", pid, __LINE__); #endif if (*cmd->argv[i] == '~') { PathAnalyze(&cmd->argv[i]); } } #ifndef NDEBUG printf("DEBUG:argv[0]:%s\n", cmd->argv[0]); #endif for (int IO_Steam = 0; IO_Steam < 3; IO_Steam++) { if (cmd->Redirect_FD[IO_Steam] >= 0 && dup2(cmd->Redirect_FD[IO_Steam], IO_Steam) == -1) { myerror("dup2", __LINE__); } } execvp(cmd->argv[0], cmd->argv); myerror("exec", __LINE__); }
|
首先执行fork()
,创建子进程,然后子进程根据struct COMMAND
的指示完成输入输出的重定向,并在struct COMMAND
中找到作为新的进程的调用参数的argv
.好了,直接调用即可.如果在未出错的情况下,程序不该执行到 第31
行,故在执行到第31
行时说明程序已出错.
内置命令
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
| _Bool BuiltInCommand(struct COMMAND *cmd) { if (strcmp(cmd->argv[0], "history") == 0) { HIST_ENTRY **history = NULL; history = history_list(); for (int i = 0; history[i] != NULL; i++) { printf("%s\n", history[i]->line); } return 0; } if (strcmp(cmd->argv[0], "cd") == 0) { if (*cmd->argv[1] == '-') { chdir(oldpath); } else if (*cmd->argv[1] == '~') { PathAnalyze(&cmd->argv[1]); } oldpath = getcwd(NULL, 0); chdir(cmd->argv[1]); return 0; } if (strcmp(cmd->argv[0], "exit") == 0 || strcmp(cmd->argv[0], "q") == 0) { exit(EXIT_SUCCESS); } return 1; }
|
收尾工作
屏蔽相关信号
1 2 3 4 5
| signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); signal(SIGTSTP, SIG_IGN);
|
输出颜色
在 main()
中,笔者希望命令提示符和当前工作目录的输出为红色.因此对代码做了如下的改动:
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
| int main(void) { signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); signal(SIGTSTP, SIG_IGN);
read_history(NULL); char Prompt[P_SIZE]; while (1) { strcpy(Prompt, RED); char *pwd = getcwd(NULL, 0); strncat(Prompt, pwd, 100); free(pwd); strcat(Prompt, " MYSHELL$" CLOSE);
char *command = readline(Prompt); add_history(command); write_history(NULL); launch(command); free(command); }
if (oldpath != NULL) { free(oldpath); } }
|
反思
必要说明
笔者在本文中launch()
的实现很低效,实际上不先行对管道数量进行计数是完全可行的.
在analyze()
中不去复制字符串也是完全可行的.
还有,丢弃掉strtok_r()
,自己实现查找和分割能比本文中的代码高效不止一点点.
笔者也曾想过是否要把文中的代码做一次重构之后在发出来,这样读者们便能看到一个更好的版本.
但笔者最终没有这样做主要是为了激励自己在日后的程序设计过程中更加深入的思考.当然,笔者相信,这点小小的修改一定难不到聪明的读者们,欢迎读者们修改本文中的代码,实现更高效的程序.
不够友善的错误处理
在本文中,笔者采用了最简单也最不友好的方式处理一切的错误.
但这种处理方式并不总是合理的,例如在myshell
中,输入错误的指令导致报错是一个常见但并不致命的错误.但笔者依旧采取了这种最简单的错误处理方式,确实未能人性化的设计程序.
不够合理的调用方式
注意launch()
中
1 2 3 4
| if (BuiltInCommand(&cmd[i])) { execute(&cmd[i]); }
|
笔者认为此处的设计并不合理,笔者认为更合理的做法可能是将
execute()
交由
BuiltInCommand()
在判断出本段命令不是内置命令之后自动调用,而不是判断
BuiltInCommand()
的返回至然后在调用
execute()
.
回看近3个月前笔者自己写出的myshell
,笔者不得不承认自己的能力是多么的有限.万幸的是,笔者在这3个月中也得到了足够的提高,才能看出原来写的程序的问题.
测试环境
GNU bash
: 5.1.4(1)-release
zsh
: 5.8
OS
: Arch Linux
Kernel
: 5.9.14-arch1-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 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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
| #define NDEBUG #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h>
#include <readline/history.h> #include <readline/readline.h> extern char **environ; #define P_SIZE 128 #define RED "\033[31m" #define CLOSE "\033[0m" struct COMMAND { int argc; int Redirect_FD[3]; char **argv; }; char *oldpath; void PathAnalyze(char **path); int OpenFile(char *string, int flags); void execute(struct COMMAND *cmd); _Bool BuiltInCommand(struct COMMAND *cmd); void launch(char *command); void myerror(char *string, int line); _Bool analyze(char *string, struct COMMAND *cmd); int main(void) { signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); signal(SIGTSTP, SIG_IGN); read_history(NULL); char Prompt[P_SIZE]; while (1) { strcpy(Prompt, RED); char *pwd = getcwd(NULL, 0); strncat(Prompt, pwd, 100); free(pwd); strcat(Prompt, " MYSHELL$" CLOSE);
char *command = readline(Prompt); add_history(command); write_history(NULL); launch(command); free(command); }
if (oldpath != NULL) { free(oldpath); } } void launch(char *command) { int pipe = 0; for (char *pr = command; *pr != '\0'; pr++) { if (*pr == '|') { pipe++; } } struct COMMAND *cmd = (struct COMMAND *)calloc(pipe + 1, sizeof(struct COMMAND)); if (cmd == NULL) { myerror("malloc", __LINE__); } char *remain = NULL; char *part = strtok_r(command, "|", &remain); for (int i = 0; i <= pipe; i++) { cmd[i].Redirect_FD[STDIN_FILENO] = -1; cmd[i].Redirect_FD[STDOUT_FILENO] = -1; cmd[i].Redirect_FD[STDERR_FILENO] = -1; } for (int i = 0; i <= pipe; i++) {
if (pipe && i < pipe) { char TempFile[] = "/tmp/MyShell_XXXXXX"; int TempFile_FD = mkstemp(TempFile); if (TempFile_FD == -1) { myerror("mkstemp", __LINE__); } cmd[i].Redirect_FD[STDOUT_FILENO] = TempFile_FD; cmd[i + 1].Redirect_FD[STDIN_FILENO] = TempFile_FD; unlink(TempFile); } analyze(part, &cmd[i]); if (BuiltInCommand(&cmd[i])) { execute(&cmd[i]); } if (pipe && i < pipe) { lseek(cmd[i].Redirect_FD[STDOUT_FILENO], 0, SEEK_SET); cmd[i].Redirect_FD[STDOUT_FILENO] = -1; } part = strtok_r(NULL, "|", &remain); for (int IO_Steam = 0; IO_Steam < 3; IO_Steam++) { if (cmd[i].Redirect_FD[IO_Steam] >= 0) { close(cmd[i].Redirect_FD[IO_Steam]); } }
for (int j = 0; j < cmd[i].argc; j++) { free(cmd[i].argv[j]); } free(cmd[i].argv); } free(cmd); }
int OpenFile(char *string, int flags) {
size_t len = 0; char *pr = string; while (!isspace(*pr) && *pr++ != '\0') { len++; }
char *dest = malloc((len + 1) * sizeof(char)); if (dest == NULL) { myerror("malloc failed", __LINE__); } strncpy(dest, string, len); dest[len] = '\0'; memset(string, ' ', sizeof(char) * len); PathAnalyze(&dest); int fd = open(dest, flags, S_IRUSR | S_IWUSR); if (fd == -1) { printf("error:fd:%d path:%s\n", fd, string); myerror("open", __LINE__); } free(dest); return fd; } void myerror(char *string, int line) { fprintf(stderr, "\aLine:%d,error:\a\n", line); fprintf(stderr, "%s:%s\n", string, strerror(errno)); exit(EXIT_FAILURE); } _Bool analyze(char *OriginString, struct COMMAND *cmd) {
char *string = strdup(OriginString); if (string == NULL) { myerror("malloc", __LINE__); } char *end = string + strlen(string); char *result = NULL; #ifndef NDEBUG printf("DEBUG:string:%s\n", string); #endif while ((result = strchr(string, '>')) != NULL) { *result = ' '; int IO_Steam = 1;
result--; if (result > string && isdigit(*result)) { if (*result - '0' != STDOUT_FILENO && *result - '0' != STDERR_FILENO) { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } else { IO_Steam = *result - '0'; *result = ' '; } } if (cmd->Redirect_FD[IO_Steam] >= 0) { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } result += 2; _Bool Append = 0; if (result < end && *result == '>') { Append = 1; *result = ' '; } while (result < end && isspace(*result)) { result++; }
if (result < end) { cmd->Redirect_FD[IO_Steam] = OpenFile(result, O_WRONLY | O_CREAT | (Append ? O_APPEND : O_TRUNC)); } else { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } } while ((result = strchr(string, '<')) != NULL) { *result = ' '; if (cmd->Redirect_FD[STDIN_FILENO] >= 0) { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } while (result < end && isspace(*result)) { result++; } if (result < end) { cmd->Redirect_FD[STDIN_FILENO] = OpenFile(result, O_RDONLY); } else { printf("Unknow COMMAND\n"); exit(EXIT_FAILURE); } }
int arg_max = 16;
cmd->argv = (char **)calloc(arg_max, sizeof(char *)); if (cmd->argv == NULL) { myerror("malloc", __LINE__); } char *remain = NULL; result = strtok_r(string, " ", &remain); while (result != NULL) { if (arg_max < cmd->argc) { arg_max *= 2; cmd->argv = (char **)realloc(cmd->argv, arg_max * sizeof(char *)); } cmd->argv[cmd->argc++] = strdup(result); if (cmd->argv == NULL) { myerror("malloc", __LINE__); } result = strtok_r(NULL, " ", &remain); } if (arg_max < cmd->argc) { arg_max++; cmd->argv = (char **)realloc(cmd->argv, arg_max * sizeof(char *)); if (cmd->argv == NULL) { myerror("malloc", __LINE__); } } cmd->argv[cmd->argc] = NULL; free(string); return 0; } void PathAnalyze(char **path) { char *RelativePath = *path; if (isalpha(*(RelativePath + 1))) { *path = malloc(strlen(RelativePath) + 1 + strlen("/home/") + 1); if (*path == NULL) { myerror("malloc", __LINE__); } strcpy(*path, "/home/"); } else { char *home = getenv("HOME"); *path = malloc(strlen(RelativePath) + 1 + strlen(home) + 1); if (*path == NULL) { myerror("malloc", __LINE__); } strcpy(*path, home); } strcat(*path, RelativePath + 1); free(RelativePath); } void execute(struct COMMAND *cmd) {
pid_t pid = fork(); if (pid > 0) { wait(NULL); return; } for (int i = 1; i < cmd->argc; i++) { #ifndef NDEBUG printf("DEBUG,pid: %d LINE:%d\n", pid, __LINE__); #endif if (*cmd->argv[i] == '~') { PathAnalyze(&cmd->argv[i]); } } #ifndef NDEBUG printf("DEBUG:argv[0]:%s\n", cmd->argv[0]); #endif for (int IO_Steam = 0; IO_Steam < 3; IO_Steam++) { if (cmd->Redirect_FD[IO_Steam] >= 0 && dup2(cmd->Redirect_FD[IO_Steam], IO_Steam) == -1) { myerror("dup2", __LINE__); } } execvp(cmd->argv[0], cmd->argv); myerror("exec", __LINE__); }
_Bool BuiltInCommand(struct COMMAND *cmd) { if (strcmp(cmd->argv[0], "history") == 0) { HIST_ENTRY **history = NULL; history = history_list(); for (int i = 0; history[i] != NULL; i++) { printf("%s\n", history[i]->line); } return 0; } if (strcmp(cmd->argv[0], "cd") == 0) { if (*cmd->argv[1] == '-') { chdir(oldpath); } else if (*cmd->argv[1] == '~') { PathAnalyze(&cmd->argv[1]); } oldpath = getcwd(NULL, 0); chdir(cmd->argv[1]); return 0; } if (strcmp(cmd->argv[0], "exit") == 0 || strcmp(cmd->argv[0], "q") == 0) { exit(EXIT_SUCCESS); } return 1; }
|
参考资料
1. 童永清.Linux C 编程实战[M].第1版.北京:人民邮电出版社 ↩
2. W.RichardStevens.Stephen.UNIX环境高级编程[M].第3版.戚正伟,译.北京:人民邮电出版社 ↩
3. Linux Programmer’s Manual ↩
4. General Commands Manual ↩
5. 鸟哥.鸟哥的Linux私房菜[M].第四版.北京:人民邮电出版社 ↩