制作Linux shell时流重定向和管道

我有一个在C中创建Linux shell的任务。目前,我仍然坚持实现重定向和管道。 我到目前为止的代码如下。 main()解析用户的输入。 如果内置命令,则执行该命令。 否则,标记化的输入传递给execute()(我知道我应该将内置命令拉入它们自己的函数中)。

execute()的作用是遍历数组。 如果遇到<>| 它应该采取适当的行动。 我试图正常工作的第一件事是管道。 不过,我肯定做错了,因为即使是一个烟斗也无法让它工作。 例如,输入/输出示例:

 /home/ad/Documents> ls -l | grep sh |: sh: No such file or directory | 

我的想法是让每个方向和管道只适用于一个案例,然后通过使函数递归,我希望在同一命令行中使用多个重定向/管道。 例如,我可以执行program1 output1.txtls -l | grep sh > output2.txt ls -l | grep sh > output2.txt

我希望有人可以在试图管道时指出我的错误,并且可能提供一些关于如何处理用户输入多个重定向/管道的情况的指示。

 #include  #include  #include  #include  #include  int MAX_PATH_LENGTH = 1024; //Maximum path length to display. int BUF_LENGTH = 1024; // Length of buffer to store user input char * delims = " \n"; // Delimiters for tokenizing user input. const int PIPE_READ = 0; const int PIPE_WRITE = 1; void execute(char **argArray){ char **pA = argArray; int i = 0; while(*pA != NULL) { if(strcmp(argArray[i],"<") == 0) { printf("") == 0) { printf(">\n"); } else if(strcmp(argArray[i],"|") == 0) { int fds[2]; pipe(fds); pid_t pid; if((pid = fork()) == 0) { dup2(fds[PIPE_WRITE], 1); close(fds[PIPE_READ]); close(fds[PIPE_WRITE]); char** argList; memcpy(argList, argArray, i); execvp(argArray[0], argArray); } if((pid = fork()) == 0) { dup2(fds[PIPE_READ], 0); close(fds[PIPE_READ]); close(fds[PIPE_WRITE]); execvp(argArray[i+1], pA); } close(fds[PIPE_READ]); close(fds[PIPE_WRITE]); wait(NULL); wait(NULL); printf("|\n"); } else { if(pid == 0){ execvp(argArray[0], argArray); printf("Command not found.\n"); } else wait(NULL);*/ } *pA++; i++; } } int main () { char path[MAX_PATH_LENGTH]; char buf[BUF_LENGTH]; char* strArray[BUF_LENGTH]; /** * "Welcome" message. When mash is executed, the current working directory * is displayed followed by >. For example, if user is in /usr/lib/, then * mash will display : * /usr/lib/> **/ getcwd(path, MAX_PATH_LENGTH); printf("%s> ", path); fflush(stdout); /** * Loop infinitely while waiting for input from user. * Parse input and display "welcome" message again. **/ while(1) { fgets(buf, BUF_LENGTH, stdin); char *tokenPtr = NULL; int i = 0; tokenPtr = strtok(buf, delims); if(strcmp(tokenPtr, "exit") == 0){ exit(0); } else if(strcmp(tokenPtr, "cd") == 0){ tokenPtr = strtok(NULL, delims); if(chdir(tokenPtr) != 0){ printf("Path not found.\n"); } getcwd(path, MAX_PATH_LENGTH); } else if(strcmp(tokenPtr, "pwd") == 0){ printf("%s\n", path); } else { while(tokenPtr != NULL) { strArray[i++] = tokenPtr; tokenPtr = strtok(NULL, delims); } execute(strArray); } bzero(strArray, sizeof(strArray)); // clears array printf("%s> ", path); fflush(stdout); } } 

部分问题出在管道处理代码中 – 正如您所怀疑的那样。

 else if (strcmp(argArray[i], "|") == 0) { int fds[2]; pipe(fds); pid_t pid; if ((pid = fork()) == 0) { dup2(fds[PIPE_WRITE], 1); close(fds[PIPE_READ]); close(fds[PIPE_WRITE]); char** argList; memcpy(argList, argArray, i); execvp(argArray[0], argArray); } if ((pid = fork()) == 0) { dup2(fds[PIPE_READ], 0); close(fds[PIPE_READ]); close(fds[PIPE_WRITE]); execvp(argArray[i+1], pA); } close(fds[PIPE_READ]); close(fds[PIPE_WRITE]); wait(NULL); wait(NULL); printf("|\n"); } 

第一个execvp()可能是为了使用argList因为你刚刚复制了一些材料。 但是,你已经复制了i个字节,而不是i字符指针,并且你没有确保管道被切换并用空指针替换。

 memcpy(argList, argArray, i * sizeof(char *)); argList[i] = 0; execvp(argList[0], argList); 

请注意,这还没有validationargList上没有缓冲区溢出; 请注意,没有为argList分配空间; 如果你使用它,你应该在执行memcpy()之前分配内存。

或者,更简单地说,您可以不使用副本。 由于您处于子进程中,因此您可以简单地使用空指针替换argArray[i] ,而不会影响父进程或其他子进程:

 argArray[i] = 0; execvp(argArray[0], argArray); 

您可能还会注意到execvp()的第二次调用使用了一个无法看到的变量pA ; 它几乎肯定是错误的初始化。 作为一个适度的经验法则,你应该写:

 execvp(array[n], &array[n]); 

上面的调用不符合这个模式,但如果你遵循它,你就不会出错。

在每个execvp()之后,您还应该有基本的错误报告和exit(1) (或者可能是_exit(1)_Exit(1) ),这样如果子execvp()执行失败,子execvp()就不会继续。 execvp()没有成功返回,但execvp()肯定会返回。

最后,现在,这些对execvp()调用应该是你进行递归调用的地方。 在尝试处理其他I / O重定向之前,您需要处理管道。 请注意,在标准shell中,您可以执行以下操作:

 > output < input command -opts arg1 arg2 

这是常规用法,但实际上是允许的。

一件好事 - 您已确保来自pipe()的原始文件描述符在所有三个进程(父级和两个子级)中都已关闭。 这是你避免犯的常见错误; 做得好。