简体   繁体   English

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

[英]Stream redirection and pipes when making a Linux shell

I have an assignment to create a Linux shell in C. Currently, I am stuck on implementing redirections and pipes. 我有一个在C中创建Linux shell的任务。目前,我仍然坚持实现重定向和管道。 The code that I have so far is below. 我到目前为止的代码如下。 The main() parses user's input. main()解析用户的输入。 If the command is built in, then that command is executed. 如果内置命令,则执行该命令。 Otherwise, the tokenized input is passed to execute() (I know that I should probably pull the built-in commands into their own function). 否则,标记化的输入传递给execute()(我知道我应该将内置命令拉入它们自己的函数中)。

What execute() does is loop through the array. execute()的作用是遍历数组。 If it encounters < , > , or | 如果遇到<>| it should take appropriate action. 它应该采取适当的行动。 The first thing I am trying to get to work correctly is piping. 我试图正常工作的第一件事是管道。 I am definitely doing something wrong, though, because I cannot get it to work for even one pipe. 不过,我肯定做错了,因为即使是一个烟斗也无法让它工作。 For example, a sample input/output: 例如,一个示例输入/输出:

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

My idea was to get each of the directions and piping work for just one case, and then by making the function recursive I could hopefully use multiple redirections/pipes in the same command line. 我的想法是让每个方向和管道仅适用于一个案例,然后通过使函数递归,我希望在同一命令行中使用多个重定向/管道。 For example, I could do program1 < input1.txt > output1.txt or ls -l | grep sh > output2.txt 例如,我可以执行program1 < input1.txt > output1.txtls -l | grep sh > output2.txt ls -l | grep sh > output2.txt . ls -l | grep sh > output2.txt

I was hoping that someone can point out my errors in trying to pipe and perhaps offer some pointers in how to approach the case where multiple redirections/pipes are inputted by the user. 我希望有人可以在尝试管道时指出我的错误,并且可能提供一些关于如何处理用户输入多个重定向/管道的情况的指示。

#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);
  }

}

Part of the problem is in the pipe handling code - as you suspected. 部分问题出在管道处理代码中 - 正如您所怀疑的那样。

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");
}

The first execvp() was probably intended to use argList since you've just copied some material there. 第一个execvp()可能是为了使用argList因为你刚刚复制了一些材料。 However, you've copied i bytes, not i character pointers, and you've not ensured that the pipe is zapped and replaced with a null pointer. 但是,你已经复制了i个字节,而不是i字符指针,并且你没有确保管道被切换并用空指针替换。

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

Note that this has not verified that there is no buffer overflow on argList ; 请注意,这还没有验证argList上没有缓冲区溢出; Note that there is no space allocated for argList ; 请注意,没有为argList分配空间; if you use it, you should allocate the memory before doing the memcpy() . 如果你使用它,你应该在执行memcpy()之前分配内存。

Alternatively, and more simply, you can do without the copy. 或者,更简单地说,您可以不使用副本。 Since you're in a child process, you can simply zap replace argArray[i] with a null pointer without affecting either the parent or the other child process: 既然你在一个子进程的时候,你可以简单地更换扎普 argArray[i]用而不影响父母或其他子处理的空指针:

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

You might also note that the second invocation of execvp() uses a variable pA which cannot be seen; 您可能还会注意到execvp()的第二次调用使用了一个无法看到的变量pA ; it is almost certainly incorrectly initialized. 它几乎肯定是错误的初始化。 As a moderately good rule of thumb, you should write: 作为一个适度的经验法则,你应该写:

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

The invocations above don't conform to this schema, but if you follow it, you won't go far wrong. 上面的调用不符合这种模式,但如果你遵循它,你就不会出错。

You should also have basic error reporting and a exit(1) (or possibly _exit(1) or _Exit(1) ) after each execvp() so that the child does not continue if it fails to execute. 在每个execvp()之后,您还应该有基本的错误报告和exit(1) (或者可能是_exit(1)_Exit(1) ),这样如果子execvp()执行失败,子execvp()就不会继续。 There is no successful return from execvp() , but execvp() most certainly can return. execvp()没有成功返回,但execvp()肯定会返回。

Finally for now, these calls to execvp() should presumably be where you make your recursive call. 最后,对于execvp()这些调用应该是你进行递归调用的地方。 You need to deal with pipes before trying to deal with other I/O redirection. 在尝试处理其他I / O重定向之前,您需要处理管道。 Note that in a standard shell, you can do: 请注意,在标准shell中,您可以执行以下操作:

> output < input command -opts arg1 arg2

This is aconventional usage, but is actually permitted. 这是常规用法,但实际上是允许的。

One good thing - you have ensured that the original file descriptors from pipe() are closed in all three processes (parent and both children). 一件好事 - 您已确保来自pipe()的原始文件描述符在所有三个进程(父级和两个子级)中都已关闭。 This is a common mistake which you have avoided making; 这是你避免犯的常见错误; well done. 做得好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM