【问题标题】:Stream redirection and pipes when making a Linux shell制作 Linux shell 时的流重定向和管道
【发布时间】:2011-09-26 00:40:01
【问题描述】:

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

execute() 的作用是遍历数组。如果遇到<>|,它应该采取适当的措施。我试图正确工作的第一件事是管道。不过,我肯定做错了什么,因为我什至无法让它为一根管道工作。例如,一个示例输入/输出:

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

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

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

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

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("<\n"); 
    }
    else if(strcmp(argArray[i],">") == 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);
  }

}

【问题讨论】:

  • 不要在 main 上执行隐式 int;明确:int main(void) 或(更合理的是,一般来说,int main(int argc, char **argv)。不要这样做 else {} 不要留下注释掉的代码(尤其是在 SO 问题中)。更系统地缩进你的代码;整个函数体应至少缩进一级(除了左大括号和右大括号)。
  • 感谢您的建议。我肯定在努力改进我的代码。
  • 错误信息说:你正在尝试执行“sh”而不是“grep”。

标签: c io redirect pipe


【解决方案1】:

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

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 字符指针,并且您没有确保管道被 zapped 和 替换为空指针。

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

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

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

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

您可能还注意到execvp() 的第二次调用使用了一个变量pA,它是看不到的;几乎可以肯定它被错误地初始化了。作为一个比较好的经验法则,你应该这样写:

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

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

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

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

> output < input command -opts arg1 arg2

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

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

【讨论】:

  • 感谢您的回复。我想知道您能否解释一下“zap argArray[i]”是什么意思?
  • 当我写'zap argArray[i]'时,我的意思是用零(空)指针覆盖它,如修改后的代码所示。作为execvp() 的第二个参数传递的字符指针数组必须以空指针终止。您带有管道符号的错误消息可能与参数列表中管道的外观有关。
  • 我明白了!我还意识到对于第二个 execvp() 我应该增加第二个参数,因为它实际上仍然指向管道。我将尝试实施这些更改,我会告诉你它是如何工作的。
  • 单管现在可以完美运行。我非常感谢您的意见和建议 :) 作为临别说明,您认为递归是多管道和重定向的方法,还是迭代算法更有意义?
  • 我还没有研究过 shell 是如何实际实现这些东西的。我倾向于认为它是迭代的而不是递归的,但这是一个有根据的猜测。您必须在适当的边界(有时是分号、管道、换行符,加上更深奥的运算符,如||&amp;&amp;,更不用说shell 流控制语句等)拆分命令,然后处理他们适当地。完成后,您可以迭代或递归处理;它们大致相等。
猜你喜欢
  • 1970-01-01
  • 2023-03-04
  • 1970-01-01
  • 2016-10-03
  • 1970-01-01
  • 2016-06-03
  • 2018-03-24
  • 2012-03-22
  • 1970-01-01
相关资源
最近更新 更多