在 C 中创建一个处理重定向和管道的 shell

标签 c shell

下面的函数成功执行了任何不包含管道的命令,所以不用担心奇怪的函数。这些工作。我遇到的问题是每当我执行如下命令时:

cat file.txt | grep string

该命令已成功执行,但它仍处于空闲状态,因此不知何故它卡住了,无法执行其他命令。为什么会这样?我认为这与我使用 pipe、dup 和 fork 的方式有关,所以尝试从这些功能来解决问题。我知道您可能会争辩说此代码不适用于其他带有管道的命令,但我只是想让这个特定示例起作用,为此我只是在第一次迭代中将 STDIN 重定向到打开的文件。

    int myshell_execute(struct processNode* list, int a)
    {

      struct processNode* myList = list; // next node to be handled
      int pipefd[2];
      int in=0;
      if (pipe(pipefd) == -1) {
            perror("pipe");
            myshell_exit(-1);
        }

      while(myList != NULL)
      {

      char* program = myList->program;  // Get the program to be executed
      char ** program_args = myList->program_arguments; // get the programs and arguments to be executed
      char ** redirection_string = myList->redirection; //get the part of the command that contains redirection
      int *status;
      int* stdout;
      int stdout_num = 1;
      stdout = &stdout_num;
      int fileDescriptor;
      pid_t pid;

        if(strcmp(program,"cd") == 0)
        {
          return myshell_cd(program_args);
        }
        else if (strcmp(program,"exit") == 0)
        {
          return myshell_exit(0);
        }


      pid = fork();

      if(pid == 0)
      {  

        if(in == 1)
        {
         close(pipefd[1]);
         dup2(pipefd[0],0);
         close(pipefd[0]);
        }

        if(sizeOfLine(redirection_string) != 0)
        {
        redirectionHandler(redirection_string,stdout); // This works. This just handles redirection properly
        }

        if(*stdout == 1 && myList->next !=NULL)
        { 
          close(pipefd[0]);
          dup2(pipefd[1],STDOUT_FILENO); // with this
          close(pipefd[1]);
        }
        if(execvp(program,program_args) !=-1)
        {
          perror("myshell:");
          myshell_exit(-1);
        }
        else{
          myshell_exit(0);
        }
      }
      else if (pid <0)
        {
          perror("myshell: ");
          myshell_exit(-1);
       }
      else
       {
         wait(status);  

       }
       in = 1;
      myList = myList->next;

      }
    }

新的解决方案:

int helper_execute(int in, int out, char* program, char ** program_args, char *redirection)
    {
      pid_t pid;

      if ((pid = fork ()) == 0)
        {
          if (in != 0)
            {
              dup2 (in, 0);
              close (in);
            }

          if (out != 1)
            {
              dup2 (out, 1);
              close (out);
            }

          redirectionHandler(redirection);
          return execvp (program, program_args);
        }

      return pid;
    }


        int myshell_execute(struct processNode* list, int a)
        {
          int i;
          pid_t pid;
          int in, fd [2];
          struct processNode*new_list = list;
          char ** newProgram = new_list->program_arguments;
          char ** redirection = new_list->redirection;
          char * program = new_list->program;

          /* The first process should get its input from the original file descriptor 0.  */
          in = 0;

          /* Note the loop bound, we spawn here all, but the last stage of the pipeline.  */
          while(new_list->next != NULL)
            {
              pipe (fd);
              /* f [1] is the write end of the pipe, we carry `in` from the prev iteration.  */
              helper_execute (in, fd [1],program,newProgram,redirection);

              /* No need for the write end of the pipe, the child will write here.  */
              close (fd [1]);

              /* Keep the read end of the pipe, the next child will read from there.  */
              in = fd [0];

              new_list = new_list->next;
            }

          /* Last stage of the pipeline - set stdin be the read end of the previous pipe
             and output to the original file descriptor 1. */  
          if (in != 0)
            dup2 (in, 0);

          /* Execute the last stage with the current process. */
          char* lastProgram = new_list->program;
          char ** lastRedirection = new_list->redirection;
          char * lastPrArguments = new_list->program_arguments;
          redirectionHandler(redirection);
          return execvp (lastProgram, lastPrArguments);
        }

        int main() {
          int i=0;
          char **input;
          struct processNode* list;
          int tracker = 0;

          while ((input = getline()) != EOF) {  
              list = create_list(input);
              myshell_execute(list,0);
          }

          return 0;
        }

这个解决方案的唯一问题是,只要执行一个命令,main 就会立即检测到文件的末尾,因此它会退出 shell。

最佳答案

这是因为

  1. 你的父进程也保持管道打开,
  2. 就在 fork 之后调用wait。如果您的第一个进程 (cat) 填满了它的输出管道,并且还没有任何进程可用于使用管道的读取端,那么该进程将永远停止。

看起来像这样:

#define MAX_PIPE_LEN 256
int myshell_execute(struct processNode* list, int a)
{
    /* fd0 are the input and output descriptor for this command. fd1
       are the input and output descriptor for the next command in the
       pipeline. */
    int fd0[2] = { STDIN_FILENO, STDOUT_FILENO },
        fd1[2] = { -1, -1 };
    pid_t pids[MAX_PIPE_LEN] = { 0 };
    int pipe_len;
    struct processNode* myList; // next node to be handled
    int status;
    int failed = 0;
    for (pipe_len = 0, myList = list;
         pipe_len < MAX_PIPE_LEN && myList != NULL;
         pipe_len++, myList = myList->next) {
        char* program = myList->program;  // Get the program to be executed
        char ** program_args = myList->program_arguments; // get the programs and arguments to be executed
        char ** redirection_string = myList->redirection; //get the part of the command that contains redirection
        if(strcmp(program,"cd") == 0) {
            return myshell_cd(program_args);
        }
        else if (strcmp(program,"exit") == 0) {
            return myshell_exit(0);
        }
        if (myList->next != NULL) {
            /* The output of this command is piped into the next one */
            int pipefd[2];
            if (pipe(pipefd) == -1) {
                perror("pipe failed");
                failed = 1;
                break;
            }
            fd1[0] = pipefd[0];
            fd1[1] = fd0[1];
            fd0[1] = pipefd[1];
        }
        pids[pipe_len] = fork();
        if (pids[pipe_len] < 0) {
            perror("error: fork failed");
            failed = 1;
            break;
        }
        if (pids[pipe_len] == 0) {  
            if (fd0[0] != STDIN_FILENO) {
                if (dup2(fd0[0], STDIN_FILENO) == -1) {
                    perror("error: dup2 input failed");
                    abort();
                }
                close(fd0[0]);
            }
            if (fd0[1] != STDOUT_FILENO) {
                if (dup2(fd0[1], STDOUT_FILENO) == -1) {
                    perror("error: dup2 outut failed");
                    abort();
                }
                close(fd0[1]);
            }
            if (fd1[0] >= 0) {
                close(fd1[0]);
            }
            if (fd1[1] >= 0) {
                close(fd1[1]);
            }
            if(sizeOfLine(redirection_string) != 0) {
                redirectionHandler(redirection_string,stdout); // This works. This just handles redirection properly
            }
            execvp(program, program_args);
            perror("error: execvp failed");
            abort();
        }
        if (fd0[0] != STDIN_FILENO) {
            close(fd0[0]);
        }
        if (fd1[1] != STDOUT_FILENO) {
            close(fd0[1]);
        }
        fd0[0] = fd1[0];
        fd0[1] = fd1[1];
        fd1[0] = fd1[1] = -1;
    }
    if (myList->next) {
        fprintf(stderr, "ERROR: MAX_PIPE_LEN (%d) is too small\n",
                MAX_PIPE_LEN);
    }

    if (fd0[0] >= 0 && fd0[0] != STDIN_FILENO) {
        close(fd0[0]);
    }
    if (fd0[1] >= 0 && fd0[1] != STDOUT_FILENO) {
        close(fd0[1]);
    }
    if (fd1[0] >= 0) {
        close(fd1[0]);
    }
    if (fd1[1] >= 0 && fd1[1] != STDOUT_FILENO) {
        close(fd1[1]);
    }
    /* Now wait for the commands to finish */
    int i;
    for (i = 0; i < pipe_len; i++) {
        if (waitpid(pids[pipe_len - 1], &status, 0) == -1) {
            perror("error: waitpid failed");
            failed = 1;
        }
    }
    if (failed)
        status = -1;
    myshell_exit(status);
}

关于在 C 中创建一个处理重定向和管道的 shell,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43568345/

相关文章:

c - 如何在 C 中将变量参数默认设置为空?

c - atmega16 串行通信不工作(uart_send 和 uart_receive)

c - 如何忽略c中以 '#'开头的输入字符串?

linux - Bash 脚本在从终端启动时可以正常工作,但在 .bashrc 调用时却无法正常工作

bash - 我们什么时候需要用大括号括住 shell 变量?

c - 输出不同——无法理解逻辑

c++ - 如何使用 C 或 C++ 获取目录中的文件列表?

linux历史命令

ruby - Ubuntu 在启动时启动 Rails

bash 例程从文本文件返回给定行号的页码