UNIX管道应用及Shell实现(二)-命令解析
2018-03-25 11:14
567 查看
接上一篇,本篇主要介绍字符串处理和命令的解析。
1. 使用fgets()等函数将输入的命令存放在缓存区中。
2. 对其用空格对其进行分割(使用[strtok()][2]等字符串处理函数),解析出特殊命令符(重定向”>”,管道”|”,后台程序”&”等)
3. 识别出特殊命令:例如history,回车,exit,cd等,这些命令不能使用exceve函数进行解析和运行,需要单独处理。
4. 如果字符串中有特殊命令符,则需要对命令两边分别进行操作。
这样,除了多管道以外的其他命令和要求我们基本上都实现了,管道的操作略微复杂,我专门写一篇来增强理解。
Operating System:Design and Implementation,Third Edition
Computer Systems: A Programmer’s Perspective, 3/E
欢迎关注我的个人博客。
命令解析
完成一个命令的解析,最重要的步骤就是字符串的解析。我们如何对拿到的字符串进行分解呢?笔者的思路如下:1. 使用fgets()等函数将输入的命令存放在缓存区中。
2. 对其用空格对其进行分割(使用[strtok()][2]等字符串处理函数),解析出特殊命令符(重定向”>”,管道”|”,后台程序”&”等)
3. 识别出特殊命令:例如history,回车,exit,cd等,这些命令不能使用exceve函数进行解析和运行,需要单独处理。
4. 如果字符串中有特殊命令符,则需要对命令两边分别进行操作。
分割字符串
/* Parse the command line with space and get the argv array */ void parseline() { initStruct(&CommandInfo); buf[strlen(buf) - 1] = ' '; /*Replace trailing '\n' with space*/ /*split buf with space*/ char* token = strtok(buf, " "); while (token) { CommandInfo.argv[CommandInfo.argc++] = token; token = strtok(NULL, " "); } /*set the last command with NULL*/ CommandInfo.argv[CommandInfo.argc] = NULL; /*empty command line*/ if (CommandInfo.argc == 0) return; /*indicate whether its background Command*/ CommandInfo.background = (*(CommandInfo.argv[CommandInfo.argc - 1]) == '&'); if (CommandInfo.background) CommandInfo.argv[--CommandInfo.argc] = NULL; return; }
特殊命令处理
针对空格、history、cd等特殊命令,可以先做预处理。/*if return 1, ignore the command*/ int IgnoreCommand() { /*if no command,continue*/ if (CommandInfo.argc < 1) return 1; /*exit command*/ if (strcmp(CommandInfo.argv[0], "exit") == 0) exit(0); /*history command*/ if (strcmp(CommandInfo.argv[0], "history") == 0) { if (CommandInfo.argc == 1) /*print all the history*/ PrintCommand(-1); else { PrintCommand(atoi(CommandInfo.argv[1])); /*convert string to int*/ } return 1; } /*cd command to change directory*/ if (strcmp(CommandInfo.argv[0], "cd") == 0) { if (CommandInfo.argc > 1) { if (chdir(CommandInfo.argv[1]) == -1) { printf("error directory!\n"); } } return 1; } /*wrong command*/ if (strcmp(CommandInfo.argv[CommandInfo.argc - 1], "<") == 0 || strcmp(CommandInfo.argv[CommandInfo.argc - 1], ">") == 0 || strcmp(CommandInfo.argv[CommandInfo.argc - 1], "|") == 0) { printf("Error:command error\n"); return 1; } return 0; }
解析命令操作符
对于“>”,“<”,“>>”操作符,不需要进行管道操作,因此直接先读取文件名。int ReviseCommand() { /* if the command is empty or exit or cd or history, should ignore the command; */ if (IgnoreCommand()) return -1; int i, override = 0; /*search the command with special charactors,and store the file and type*/ for (i = 0; i < CommandInfo.argc; i++) { if (strcmp(CommandInfo.argv[i], "<") == 0) { CommandInfo.argv[i] = NULL; CommandInfo.file = CommandInfo.argv[i + 1]; CommandInfo.type[CommandInfo.index++] = IN_REDIRECT; override = 1; } else if (strcmp(CommandInfo.argv[i], ">") == 0) { /* if > is not the first command, should not set the file */ CommandInfo.argv[i] = NULL; if (!override) CommandInfo.file = CommandInfo.argv[i + 1]; CommandInfo.type[CommandInfo.index++] = OUT_REDIRECT; break; } else if (strcmp(CommandInfo.argv[i], ">>") == 0) { CommandInfo.argv[i] = NULL; if (!override) CommandInfo.file = CommandInfo.argv[i + 1]; CommandInfo.type[CommandInfo.index++] = OUT_ADD; break; } /*multi - PIPE*/ else if (strcmp(CommandInfo.argv[i], "|") == 0) { CommandInfo.type[CommandInfo.index++] = PIPE; CommandInfo.argv[i] = NULL; } } return 1; }
命令主题框架
我们首先使用parseline()对得到的命令按照空格进行解析,之后再使用ReviseCommand()提取关键命令字符,识别回车键等,最后再对进程进行fork(),子进程(ChildCommand)执行命令,父进程根据是否有“&”选择等待子进程结束或者继续执行。void command() { pid_t pid; int indicator = 0; parseline(); /*re-edit command and get the file*/ indicator = ReviseCommand(); if (indicator == -1) return; pid = fork(); if (!pid) { /*the background process should not be disturbed by CTRL+C and CTRL+\*/ /*sigaction(SIGINT, SIG_IGN, NULL); sigaction(SIGQUIT, SIG_IGN, NULL);*/ ChildCommand(); exit(0); } else { if (!CommandInfo.background) waitpid(pid, NULL, 0); else { /*if background process, the father should ignore the signal let init to reap it */ printf("there is a background process\n"); } } return; }
子进程命令框架
对于fork出来的子进程,如果只有重定向这种简单的命令,我们通过解析到的字符串和文件名就可以直接进行操作,如果涉及到多个管道的操作,那就要小心了。void ChildCommand() { int fd; switch (CommandInfo.type[0]) { case NORMAL: Execvp(CommandInfo.argv[0], CommandInfo.argv); break; case IN_REDIRECT: /* < command*/ fd = open(CommandInfo.file, O_RDONLY); if (fd == -1) { printf("Error: wrong input!\n"); break; } dup2(fd, STDIN_FILENO); if (CommandInfo.type[1] == PIPE) { EditInfo(); pipe_command(); } Execvp(CommandInfo.argv[0], CommandInfo.argv); break; case OUT_REDIRECT: /* > command*/ fd = open(CommandInfo.file, O_WRONLY | O_CREAT | O_TRUNC, 0666); dup2(fd, STDOUT_FILENO); Execvp(CommandInfo.argv[0], CommandInfo.argv); break; case OUT_ADD: /* >> command*/ fd = open(CommandInfo.file, O_RDWR | O_APPEND, 0666); dup2(fd, STDOUT_FILENO); Execvp(CommandInfo.argv[0], CommandInfo.argv); break; case PIPE: /* | command*/ pipe_command(); break; } }
这样,除了多管道以外的其他命令和要求我们基本上都实现了,管道的操作略微复杂,我专门写一篇来增强理解。
参考资料:
Linux shell的实现Operating System:Design and Implementation,Third Edition
Computer Systems: A Programmer’s Perspective, 3/E
欢迎关注我的个人博客。
相关文章推荐
- UNIX管道应用及Shell实现(三)-多管道实现
- 解析如何在C语言中调用shell命令的实现方法
- 【shell编程基础3】shell编程的组合应用之二:管道及其命令
- 解析如何在C语言中调用shell命令的实现方法【转】
- Shell实现(四) 执行命令的实现(包含管道的实现)
- UNIX命令解析——tar 命令高级应用
- shell 之 tee 命令,实现重定向到文件的同时仍能 通过管道 (|)传给接下来的命令
- 解析如何在C语言中调用shell命令的实现方法
- 使用PHP+Readline库实现类似于Shell解析命令功能
- 数据结构习作之应用 "栈(Stack)" 实现: 解析算术表达式及计算求值 (C#/Java) (技术含量少许)
- UNIX/XENIX环境下SETKEY命令的应用技巧
- UNIX环境下如何应用消息队列实现进程间通信
- jxta 的 Shell 2.4.1 比较以往版本的 建立管道的命令修改
- 深入浅出Shell 编程:Unix/Linux 命令
- 解析Cookie欺骗实现过程及具体应用[zt]
- Unix程序设计:实现wc命令
- Unix程序设计:实现cp命令
- shell 命令(Unix/Linu命令)(2)
- shell 命令(Unix/Linu命令)(1)
- 解析Cookie欺骗实现过程及具体应用