54

I'm writing a C program where I fork(), exec(), and wait(). I'd like to take the output of the program I exec'ed to write it to file or buffer.

For example, if I exec ls I want to write file1 file2 etc to buffer/file. I don't think there is a way to read stdout, so does that mean I have to use a pipe? Is there a general procedure here that I haven't been able to find?

alk
  • 69,737
  • 10
  • 105
  • 255
devin
  • 6,407
  • 14
  • 48
  • 53

6 Answers6

113

For sending the output to another file (I'm leaving out error checking to focus on the important details):

if (fork() == 0)
{
    // child
    int fd = open(file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);

    dup2(fd, 1);   // make stdout go to file
    dup2(fd, 2);   // make stderr go to file - you may choose to not do this
                   // or perhaps send stderr to another file

    close(fd);     // fd no longer needed - the dup'ed handles are sufficient

    exec(...);
}

For sending the output to a pipe so you can then read the output into a buffer:

int pipefd[2];
pipe(pipefd);

if (fork() == 0)
{
    close(pipefd[0]);    // close reading end in the child

    dup2(pipefd[1], 1);  // send stdout to the pipe
    dup2(pipefd[1], 2);  // send stderr to the pipe

    close(pipefd[1]);    // this descriptor is no longer needed

    exec(...);
}
else
{
    // parent

    char buffer[1024];

    close(pipefd[1]);  // close the write end of the pipe in the parent

    while (read(pipefd[0], buffer, sizeof(buffer)) != 0)
    {
    }
}
R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
  • 8
    You write: "close(pipefd[1]); // this descriptor is no longer needed". Why? – hoffmaje Dec 09 '12 at 07:46
  • On the line where you duplicate the file descriptor and send the stdout to the file, what does the '1' signify? I'm unable to find any documentation for this. – Matthew Brzezinski Feb 12 '16 at 03:34
  • 1
    @MattBrzezinski - 1 is the file descriptor for stdout. – R Samuel Klatchko Feb 13 '16 at 05:13
  • 1
    It would be excellent if you could extend your answer and explain how to read stdout & stderr separately, i.e. how to read from two pipes. I suppose this would involve using `select` instead and finally a `waitpid` on the child to remove the zombie? – Frerich Raabe Mar 07 '16 at 09:21
  • https://www.bottomupcs.com/file_descriptors.xhtml (found by googling *standard file descriptor numbers*) –  Jun 12 '18 at 20:03
15

You need to decide exactly what you want to do - and preferably explain it a bit more clearly.

Option 1: File

If you know which file you want the output of the executed command to go to, then:

  1. Ensure that the parent and child agree on the name (parent decides name before forking).
  2. Parent forks - you have two processes.
  3. Child reorganizes things so that file descriptor 1 (standard output) goes to the file.
  4. Usually, you can leave standard error alone; you might redirect standard input from /dev/null.
  5. Child then execs relevant command; said command runs and any standard output goes to the file (this is the basic shell I/O redirection).
  6. Executed process then terminates.
  7. Meanwhile, the parent process can adopt one of two main strategies:
    • Open the file for reading, and keep reading until it reaches an EOF. It then needs to double check whether the child died (so there won't be any more data to read), or hang around waiting for more input from the child.
    • Wait for the child to die and then open the file for reading.
    • The advantage of the first is that the parent can do some of its work while the child is also running; the advantage of the second is that you don't have to diddle with the I/O system (repeatedly reading past EOF).

Option 2: Pipe

If you want the parent to read the output from the child, arrange for the child to pipe its output back to the parent.

  1. Use popen() to do this the easy way. It will run the process and send the output to your parent process. Note that the parent must be active while the child is generating the output since pipes have a small buffer size (often 4-5 KB) and if the child generates more data than that while the parent is not reading, the child will block until the parent reads. If the parent is waiting for the child to die, you have a deadlock.
  2. Use pipe() etc to do this the hard way. Parent calls pipe(), then forks. The child sorts out the plumbing so that the write end of the pipe is its standard output, and ensures that all other file descriptors relating to the pipe are closed. This might well use the dup2() system call. It then executes the required process, which sends its standard output down the pipe.
  3. Meanwhile, the parent also closes the unwanted ends of the pipe, and then starts reading. When it gets EOF on the pipe, it knows the child has finished and closed the pipe; it can close its end of the pipe too.
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
2

Since you look like you're going to be using this in a linux/cygwin environment, you want to use popen. It's like opening a file, only you'll get the executing programs stdout, so you can use your normal fscanf, fread etc.

Blindy
  • 65,249
  • 10
  • 91
  • 131
1

After forking, use dup2(2) to duplicate the file's FD into stdout's FD, then exec.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

You could also use the linux sh command and pass it a command that includes the redirection:

string cmd = "/bin/ls > " + filepath;

execl("/bin/sh", "sh", "-c", cmd.c_str(), 0);
Jasper Koning
  • 143
  • 1
  • 9
  • One should be very cautious when using this approach, especially when `cmd` is computed from user input. If an attacker can control `cmd`, for example because `filepath` is read from a configuration file, it is very easy to construct a shell injection. In this case, setting `filepath` to `/dev/null ; echo 'Got you'` would suffice. – PEAR Jun 27 '23 at 12:01
0

For those such as myself who like a complete example with includes, here's this fantastic answer with a runnable example (still without error handling, left as an exercise):

#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    if (fork() == 0) { // child
        int fd = open("test.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);

        dup2(fd, 1);   // make stdout go to file
        dup2(fd, 2);   // make stderr go to file - you may choose to not do this
                       // or perhaps send stderr to another file

        close(fd);     // fd no longer needed - the dup'ed handles are sufficient

        execlp("ls", "ls", NULL);
    }
    else {
        while (wait(NULL) > 0) {} // wait for each child process
    }

    return 0;
}
ggorlen
  • 44,755
  • 7
  • 76
  • 106