您的位置:首页 > 运维架构 > Shell

UNIX管道应用及Shell实现(二)-命令解析

2018-03-25 11:14 567 查看
接上一篇,本篇主要介绍字符串处理和命令的解析。

命令解析

完成一个命令的解析,最重要的步骤就是字符串的解析。我们如何对拿到的字符串进行分解呢?笔者的思路如下:

1. 使用fgets()等函数将输入的命令存放在缓存区中。

2. 对其用空格对其进行分割(使用[strtok()][2]等字符串处理函数),解析出特殊命令符(重定向”>”,管道”|”,后台程序”&”等)

3. 识别出特殊命令:例如history回车exitcd等,这些命令不能使用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

欢迎关注我的个人博客
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Shell OS 管道