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

发布于 2024-12-06 05:28:47 字数 3473 浏览 0 评论 0原文

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

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

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

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

}

I have an assignment to create a Linux shell in C. Currently, I am stuck on implementing redirections and pipes. The code that I have so far is below. The main() parses user's input. 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).

What execute() does is loop through the array. 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.

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

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

緦唸λ蓇 2024-12-13 05:28:47

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

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]);

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

您还应该在每个 < code>execvp() 以便子进程在执行失败时不会继续。 execvp() 没有成功返回,但 execvp() 肯定可以返回。

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

> output < input command -opts arg1 arg2

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

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

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. 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.

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; Note that there is no space allocated for argList; if you use it, you should allocate the memory before doing the 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] = 0;
execvp(argArray[0], argArray);

You might also note that the second invocation of execvp() uses a variable pA which cannot be seen; 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. There is no successful return from execvp(), but execvp() most certainly can return.

Finally for now, these calls to execvp() should presumably be where you make your recursive call. You need to deal with pipes before trying to deal with other I/O redirection. Note that in a standard shell, you can do:

> 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). This is a common mistake which you have avoided making; well done.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文