如何将管道命令的输出重定向到文件?

3

我有一个解析给定命令并将所有参数/程序分配给结构体的程序。在执行命令的主程序中,如果给出">",我尝试将管道命令的输出重定向到文件。例如,我的程序将成功执行以下命令:

cat filea | grep pattern

但我也希望能够执行该命令

cat filea | grep pattern > outfile

作为一个附注,了解cmdscan.c的确切机制并不是非常重要,因为它被作为一种辅助程序提供,以帮助解析命令字符串并填充结构值,这使得在主程序hsh.c中检查情况更容易。此外,argv1和argv2是管道的左侧和右侧,因此只有当存在管道时argv2才会被填充。如果有任何形式的重定向,则文件名将存储在infile/outfile中,具体取决于重定向的方向。
这是我的主程序hsh.c,用于执行命令:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <string.h>

#define BUFSIZE 500
struct cmd
  {
    int redirect_in;     /* Any stdin redirection?         */
    int redirect_out;    /* Any stdout redirection?        */
    int redirect_append; /* Append stdout redirection?     */
    int background;      /* Put process in background?     */
    int piping;          /* Pipe prog1 into prog2?         */
    char *infile;        /* Name of stdin redirect file    */
    char *outfile;       /* Name of stdout redirect file   */
    char *argv1[10];     /* First program to execute       */
    char *argv2[10];     /* Second program in pipe         */
  };

int cmdscan(char *cmdbuf, struct cmd *com);

int main() {
    char buf[BUFSIZE];
    struct cmd command;
    pid_t pid;
    int status;
    int fd[2];
    pipe(fd);
    int fdout;

    while((fgets(buf,BUFSIZE,stdin) != NULL)) {

    if(cmdscan(buf,&command)==-1) {
        printf("illegal format\n"); 
        continue; 
    }


    if((pid=fork()) <0) 
        perror("fork error\n"); 

    if(strcmp(command.argv1[0],"exit") == 0) {
            return 0; 
        }
    else if (pid == 0) {
        //if the command has piping
        if(command.piping){
            if((pid = fork()) <0)
                perror("fork error");
            //fork again so we can do more commands after this one
            else if(pid == 0) {
                if((pid = fork()) < 0)
                    perror("fork error");
                else if (pid == 0){
                    //fdout = open(command.outfile, O_CREAT | O_WRONLY);
                    //dup2(fdout, STDOUT_FILENO);
                    dup2(fd[1], STDOUT_FILENO);
                        close(fd[1]);
                        execvp(*command.argv1,command.argv1);


                } else {
                    dup2(fd[0],STDIN_FILENO);
                    close(fd[0]);
                    execvp(*command.argv2,command.argv2);
                }
            }
        //execute normal command
        }else {
            //if normal command has redirection
            if(command.redirect_out){
                fdout = open(command.outfile, O_CREAT | O_WRONLY);
                dup2(fdout,STDOUT_FILENO);
                close(fd[0]);
                execvp(*command.argv1,command.argv1);

            }
            else{
                execvp(*command.argv1,command.argv1);
            }
        }
        //..
        exit(0);
    } else {
        if(wait(&status)!=pid)
            perror("wait error"); 
    }
     }


    return 0;
}

这是解析命令行的程序cmdscan.c。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

struct cmd
  {
    int redirect_in;     /* Any stdin redirection?         */
    int redirect_out;    /* Any stdout redirection?        */
    int redirect_append; /* Append stdout redirection?     */
    int background;      /* Put process in background?     */
    int piping;          /* Pipe prog1 into prog2?         */
    char *infile;        /* Name of stdin redirect file    */
    char *outfile;       /* Name of stdout redirect file   */
    char *argv1[10];     /* First program to execute       */
    char *argv2[10];     /* Second program in pipe         */
  };

#define TRUE 1
#define FALSE 0

int 
cmdscan(char *cmdbuf, struct cmd *com)
{
  char *token;
  char *curcmd;        /* pointer to current command string */
  char swtch[256];    /* buffer to hold copy of switch */
  char *separators = " \t\n";
  int i;

  com->redirect_in = FALSE;
  com->redirect_out = FALSE;
  com->redirect_append = FALSE;
  com->background = FALSE;
  com->piping = FALSE;

  if ( (com->argv1[0] = strtok(cmdbuf,separators) ) == NULL)
    return(-1);
  i = 1;
  while( (com->argv1[i++] = (token = strtok(NULL,separators))) != NULL && strcmp(token,">") &&
        strcmp(token,"<") && strcmp(token,">>") && strcmp(token,"&") && strcmp(token,"|") );
  com->argv1[i-1] = NULL;

  if ( token != NULL && strcmp(token,"|") == 0 )
    {
      com->piping = TRUE;
      i = 0;
      while( (com->argv2[i++] = (token = strtok(NULL,separators))) != NULL && strcmp(token,">") &&
        strcmp(token,"<") && strcmp(token,">>") && strcmp(token,"&") && strcmp(token,"|") );
      com->argv2[i-1] = NULL;
      if ( com->argv2[0] == NULL )
        return(-1);
    }

  while ( token != NULL ){

    if ( !strcmp(token,">") || !strcmp(token,">>") )
      {
        if ( com->redirect_out )
          return(-1);
        com->redirect_out = TRUE;
        if ( !strcmp(token,">>") )
          com->redirect_append = TRUE;
        if ( (com->outfile = strtok(NULL,separators)) == NULL )
          return(-1);
      } 
    else if ( !strcmp(token,"<") )
      {
        if ( com->redirect_in )
          return(-1);
        com->redirect_in = TRUE;
        if ( (com->infile = strtok(NULL,separators)) == NULL )
          return(-1);
      } 
    else if ( !strcmp(token,"|") )
      {
        if ( com->piping )
          return(-1);
      } 
    else if ( !strcmp(token,"&") )
      {
        if ( com->background )
          return(-1);
        com->background = TRUE;
        if ( (token = strtok(NULL,separators)) != NULL )
          return(-1);
        break;
      }
    else
      return(-1);

    token = strtok(NULL,separators);
  }

  return(0);
}

我尝试应用与重定向的简单命令相同的逻辑,但无法使其正常工作,并对管道感到有点困惑。


重定向实际上相当复杂,你是否考虑过像 cat filea 2>&1 | grep pattern 这样的命令,它在通过管道传递之前将 stderr 重定向到 stdout? - user229044
起初,我尝试将 stdout 更改为 fdout,类似于 else 语句中的一般命令,但是不起作用。然后我在手册中读到,在管道情况下的输出重定向应该在程序2中处理,所以我也尝试在那里处理,但是因为程序2处理来自管道读端的 STDIN,所以感到困惑。我尝试了很多不同的组合,但它不起作用。我对那个命令有点困惑。我知道“cat filea | grep pattern”可以工作。 - MeesterMarcus
当我运行该命令时,会出现“cat: 2>&: No such file or directory”的错误提示。 - MeesterMarcus
1个回答

1
在执行输出重定向之前,需要修复一个基本错误:您在main程序中创建了一个pipe,并且这个pipe一直保持打开状态,直到程序结束;每个从管道读取直到EOF的进程都不会在main程序结束之前终止,因此您在运行期间输入的管道命令行越多,就会有越多的等待进程挂起(可以使用ps观察到这一点)。为了纠正这个问题,在管道进程的fork之前创建pipe;此外,在dup2之后必须关闭两个管道端口(请参见下文)。

之后,输出重定向很简单,类似于您在if normal command has redirection情况下所做的操作,但是您违反了规则:

int open(const char *pathname, int flags, mode_t mode);

mode指定创建新文件时要使用的权限。当在flags中指定O_CREAT时,必须提供此参数;

所以你的管道代码的核心变成了:

                pipe(fd);
                if ((pid = fork()) < 0)
                    perror("fork error");
                else if (pid == 0) {
                    dup2(fd[1], STDOUT_FILENO);
                    close(fd[0]);   // close both fd in child
                    close(fd[1]);
                    execvp(*command.argv1, command.argv1);
                } else {
                    dup2(fd[0], STDIN_FILENO);
                    close(fd[0]);
                    close(fd[1]);   // close both fd in parent
                    if (command.redirect_out)
                    {
                        fdout =
                          open(command.outfile, O_CREAT|O_WRONLY|O_TRUNC, 0666);
                        dup2(fdout, STDOUT_FILENO);
                    }
                    execvp(*command.argv2, command.argv2);
                }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接