33

I'm trying to write a C program that grabs command output and then i'll be passing that to another program.

I'm having an issue, I cant work out how to get the command output and store it. Below is a sample of what I have

if(fork() == 0){
   execl("/bin/ls", "ls", "-1", (char *)0);
   /* do something with the output here */
}
else{
    //*other stuff goes here*
}

so basically im wondering if there is any way i can get the output from the "execl" and pass it to some thing else (e.g. via storing it in some kind of buffer).

Suggestions would be great.

Eric Leschinski
  • 146,994
  • 96
  • 417
  • 335
TrewTzu
  • 1,110
  • 2
  • 11
  • 27

4 Answers4

61

You have to create a pipe from the parent process to the child, using pipe(). Then you must redirect standard ouput (STDOUT_FILENO) and error output (STDERR_FILENO) using dup or dup2 to the pipe, and in the parent process, read from the pipe. It should work.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define die(e) do { fprintf(stderr, "%s\n", e); exit(EXIT_FAILURE); } while (0);

int main() {
  int link[2];
  pid_t pid;
  char foo[4096];

  if (pipe(link)==-1)
    die("pipe");

  if ((pid = fork()) == -1)
    die("fork");

  if(pid == 0) {

    dup2 (link[1], STDOUT_FILENO);
    close(link[0]);
    close(link[1]);
    execl("/bin/ls", "ls", "-1", (char *)0);
    die("execl");

  } else {

    close(link[1]);
    int nbytes = read(link[0], foo, sizeof(foo));
    printf("Output: (%.*s)\n", nbytes, foo);
    wait(NULL);

  }
  return 0;
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Aif
  • 11,015
  • 1
  • 30
  • 44
  • 1
    Thanks heaps! the great example that shows how to read from it won it for you. – TrewTzu Sep 03 '11 at 12:01
  • I can read the man pages on dup2, execl, etc, which explains the function calls, but I don't understand the overall picture. Can somebody elaborate why we need to fork? Why do we close the links when we do, and why the wait(NULL) at the end? – Damien Mar 22 '16 at 12:21
  • 2
    Indeed fork might not be needed. But if you want to perform operations at the end of the chile task, you have to fork a new process. Wait ensures that parent's process don't quit bedore child. – Aif Mar 22 '16 at 12:24
  • 1
    I found this explanation in my copy of [APUE](http://www.apuebook.com/), section 1.6: "In the child, we call `exec` to execute the command [...]. This replaces the child process with the new program file. The combination of `fork` followed by `exec` is called spawning a new process on some operating systems. In the UNIX System, the two parts are separated into individual functions." – Damien Mar 22 '16 at 13:55
  • 1
    Okay, all starting to make sense now. From the man page: "The `exec()` family of functions replaces the current process image with a new process image." – Damien Mar 22 '16 at 13:58
33

Open a pipe, and change stdout to match that pipe.

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

 int pipes[2];

 pipe(pipes); // Create the pipes

 dup2(pipes[1],1); // Set the pipe up to standard output

After that, anything which goes to stdout,(such as through printf), comes out pipe[0].

FILE *input = fdopen(pipes[0],"r");

Now you can read the output like a normal file descriptor. For more details, look at this

ceph3us
  • 7,326
  • 3
  • 36
  • 43
Darcy Rayner
  • 3,385
  • 1
  • 23
  • 15
6

Thanks Jonathan Leffler, and i optimize the above code for it can't read all response for one time.

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

#define die(e) do { fprintf(stderr, "%s\n", e); exit(EXIT_FAILURE); } while (0);

int main() {
  int link[2];
  pid_t pid;
  char foo[4096 + 1];
  memset(foo, 0, 4096);

  if (pipe(link)==-1)
    die("pipe");

   if ((pid = fork()) == -1)
    die("fork");

  if(pid == 0) {

    dup2 (link[1], STDOUT_FILENO);
    close(link[0]);
    close(link[1]);
    execl("/bin/ls", "ls", "-1", (char *)0);
    die("execl");
  } else {
    close(link[1]);
    int nbytes = 0;
    std::string totalStr;
    while(0 != (nbytes = read(link[0], foo, sizeof(foo)))) {
        totalStr = totalStr + foo;
        printf("Output: (%.*s)\n", nbytes, foo);
        memset(foo, 0, 4096);
    }
    wait(NULL);
  }
  return 0;
}
user2420449
  • 79
  • 1
  • 1
0

If you want the output in a string (char *), here's an option (for Linux at least):

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>

char* qx(char** cmd, int inc_stderr) {
  int stdout_fds[2];
  pipe(stdout_fds);

  int stderr_fds[2];
  if (!inc_stderr) {
    pipe(stderr_fds);
  }

  const pid_t pid = fork();
  if (!pid) {
    close(stdout_fds[0]);
    dup2(stdout_fds[1], 1);
    if (inc_stderr) {
      dup2(stdout_fds[1], 2);
    }

    close(stdout_fds[1]);

    if (!inc_stderr) {
      close(stderr_fds[0]);
      dup2(stderr_fds[1], 2);
      close(stderr_fds[1]);
    }

    execvp(*cmd, cmd);
    exit(0);
  }

  close(stdout_fds[1]);

  const int buf_size = 4096;
  char* out = malloc(buf_size);
  int out_size = buf_size;
  int i = 0;
  do {
    const ssize_t r = read(stdout_fds[0], &out[i], buf_size);
    if (r > 0) {
      i += r;
    }

    if (out_size - i <= 4096) {
      out_size *= 2;
      out = realloc(out, out_size);
    }
  } while (errno == EAGAIN || errno == EINTR);

  close(stdout_fds[0]);

  if (!inc_stderr) {
    close(stderr_fds[1]);
    do {
      const ssize_t r = read(stderr_fds[0], &out[i], buf_size);

      if (r > 0) {
        i += r;
      }

      if (out_size - i <= 4096) {
        out_size *= 2;
        out = realloc(out, out_size);
      }

    } while (errno == EAGAIN || errno == EINTR);

    close(stderr_fds[0]);
  }

  int r, status;
  do {
    r = waitpid(pid, &status, 0);
  } while (r == -1 && errno == EINTR);

  out[i] = 0;

  return out;
}

int main() {
  char* argv[3];
  argv[0] = "ls";
  argv[1] = "-la";
  argv[2] = NULL;
  char* out = qx(argv, 0);
  printf("%s", out);
  free(out);
}
ericcurtin
  • 1,499
  • 17
  • 20