Use a here-document with execvp

729 Views Asked by At

I'm trying to run the command "wc -l << END" using execvp, so I'm parsing the command and runing execvp with "wc -l" but then it goes into an infinite loop.
How can I make it work so it stops when it finds the keyword (in this case END)?

  • I have to use execvp
  • the command comes from a user input that in this case is "wc -l << END"

edit
Here is my code (won't help much but can give some background)[the code for the here-doc is in the last else statement]:

redirect(int proc, char * input){
    char * comm;
    int proc2;
    int append = 0;
    if(proc == 1){ //in
        comm = strsep(&input, "<");
        proc2 = check(input);
    }
    else{ //out
        comm = strsep(&input, ">");
        proc2 = check(input);
        if(proc2 == 2){ //append
            strsep(&input, ">");
            append = 1;
        }
    }

    if(proc2 == 0 || append == 1){ //only one redirection
        if(proc == 1){ //in
            input = trim(input);
            int fd = open(input, O_RDWR);
            close(0);
            dup2(fd, 0);
            close(fd);

            comm = trim(comm);
            char ** words = parse(comm);

            if(!execvp(words[0], words)){   /*exec failed */
                exit(1);
            }
        }
        else{ //out
            input = trim(input);

            int fd;
            if(append == 0){ //create
                fd = open(input, O_CREAT | O_RDWR | O_TRUNC, 
                    S_IRUSR | S_IWUSR);
            }
            else{ //append
                fd = open(input, O_CREAT | O_RDWR | O_APPEND, 
                    S_IRUSR | S_IWUSR);
            }

            dup2(fd, 1);
            close(fd);

            comm = trim(comm);
            char ** words = parse(comm);

            if(!execvp(words[0], words)){   /*exec failed */
                exit(1);
            }
        }
    }
    else{ //more than one redirection/pipe
        if(proc == proc2){ //here-doc
            strsep(&input, "<");
            input = trim(input);

        }

    }
}

A solution would be to store the user input into a temp file and then pass the temp file as stdin but I wanted to know if there is a better way to do it.

2

There are 2 best solutions below

0
On BEST ANSWER

Rather than using a temp file and pointing the child's stdin at it, you can feed the input to the child through a pipe, from either the parent or another child process. The parent or second child process would then take care of reading the input, watching for the end of input marker, and passing the data along to your (original) child process.

The following example shows how to use a pipe and a second child process to read stdin, watch for an end-of-input marker, and pass the input through to the original child process until it's found. The example omits error checking (which you should add), and uses hard-coded values for the command to run and the end of input marker. Note that it's important that the write end of the pipe be closed in the parent so that the command will see it's stdin closed after the input reader exits.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc, char *argv[])
{
  pid_t rdr,  /* input reader process */
        cmd,  /* command runner process */
        wres; /* wait() result */
  /* pipe for passing input from rdr to cmd */
  int pipefd[2] = {0};
  /* command and args to pass to execvp() */
  char *command = "wc";
  char *cmdargs[] = { "wc", "-l", NULL };
  /* end of input marker */
  char *endinp = "END\n";

  /* create a pipe: 
     - read end's fd will be pipefd[0], 
     - write end's fd will be pipefd[1] */
  pipe(pipefd);

  cmd = fork();
  if (!cmd) {  /* COMMAND RUNNER PROCESS */
    dup2(pipefd[0],0); /* set stdin to pipe's read end */
    close(pipefd[1]);  /* close pipe's write end */
    /* exec command (will read from stdin) */
    execvp(command, cmdargs);
  }

  rdr = fork();
  if (!rdr) {   /* INPUT READER PROCESS */
    close(pipefd[0]); /* close pipe's read end */

    /* read input from stdin until a line containing only
       the end of input marker is found */
    char buf[1024];
    while (fgets(buf,sizeof(buf),stdin)) {
      /* break when end of input marker is read */
      if (!strcmp(buf,endinp)) break;
      /* write data to pipe */
      write(pipefd[1],buf,strlen(buf));
    }
    return 0;
  }

  /* PARENT PROCESS */

  close(pipefd[0]); /* close pipe's read end */
  close(pipefd[1]); /* close pipe's write end */

  /* wait for both children to exit */
  do {
    wres = wait(NULL);
    if (wres == rdr) rdr = 0;
    if (wres == cmd) cmd = 0;
  } while (rdr || cmd);

  return 0;
}
0
On

Usually you post your question with your code, otherwise we won't be properly helping you to develop your programming skills. I'll give you a piece of cake this time and give you a sample code (which might be the full answer).

#include <stdio.h>
#include <unistd.h>
#include <err.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    pid_t pid;
    int status;


    // you must provide the full path for the executable
    // such as /bin/echo, /usr/bin/wc ...
    if(argc < 2)
    {
        printf("expected format example /usr/bin/wc -l << END\n");
        return (-1);
    }

    // fork your process since EXEC family
    // takes control over your process
    // This way you may do other things
    if((pid = fork()) < 0)
        err(1, "fork() error");

    if(pid == 0) /* child */
    {
        // argv[0] is the name of your program executable
        // first argument to execvp is the target executable
        // with the full path, again /bin/echo
        // segund argument is a list of arguments for your
        // target null terminated. Note that the first
        // argument of that list is the name of the executable/location
        // itself
        // illustrative: execvp("/bin/echo", {"/bin/echo", "example", NULL}) 
        if(execvp(argv[1], &argv[1]) < 0)
            err(1, "execvp() error");
    }
    else /* father */
    {
        printf(">> father waiting child..\n");
        while(wait(&status) != pid);
        printf(">> child finished\n");
    }

    return (0);
}

You might take a look at this if you want to understand deeply this subject.