MeesterMarcus
MeesterMarcus

Reputation: 710

How to redirect output from pipe command to a file

I have a program that parses the command given, and allocates all the arguments/programs to a struct. In my main program that executes the commands, I am trying to redirect the output of the pipe command to a file if a ">" is given. For example, my program will successfuly execute the command

cat filea | grep pattern

but I want to also be able to execute the command

cat filea | grep pattern > outfile

As a side note, it's not too important to understand the exact mechanics of cmdscan.c as it was given as sort of a helper program to help parse the command string and fill in the struct values which makes it easier to check for cases in the main program hsh.c. Also, the argv1 and argv2 are the left and right hand side of the pipe, so argv2 is only filled up when there is a pipe. And if there is redirection of any sort then the name of the file will be stored in infile/outfile depending on the redirection

This is my main program hsh.c that executes the commands:

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

This is the program that parses the command line, 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);
}

I tried applying the same logic as the simple command for redirection but I couldn't get it to work and got sort of confused about the pipes.

Upvotes: 3

Views: 2245

Answers (1)

Armali
Armali

Reputation: 19375

Before we get output redirection to work, there's a basic error to fix: You create one pipe in the main program, and this pipe remains open until the end of the program; every process that reads from the pipe until EOF will not terminate before the end of the main program, so the more pipe command lines you enter during a run, the more waiting processes will hang around (you can observe this with ps). To correct this, create the pipe just before the fork for the pipeline processes; in addition you must close both ends of the pipe after the dup2 (see below).

After this, the output redirection is simple, similar to what you did in case if normal command has redirection, but you violated the rule:

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

mode specifies the permissions to use in case a new file is created. This argument must be supplied when O_CREAT is specified in flags;

So the core of your piping code becomes:

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

Upvotes: 1

Related Questions