4

I have a parent process which forks out a child to perform execv().

I need the output given by execv() to stdout to be displayed onscreen as also copied to a log file.

How do I write the same output to both stdout & a file, without using pipes or tees?

  • 2
    As written, your question will trigger the "use `tee`" response from a great many people. Consider editing to make it clear that you wish to do this internally... – dmckee --- ex-moderator kitten Sep 07 '10 at 18:28
  • Ad duplicate (though slightly different usage) of this: http://stackoverflow.com/questions/1760726/compose-output-streams Though I like my answer best. – Martin York Sep 07 '10 at 19:02
  • possible duplicate of [Copy STDOUT to file without stopping it showing onscreen](http://stackoverflow.com/questions/1356706/copy-stdout-to-file-without-stopping-it-showing-onscreen) – Ether Oct 10 '10 at 23:14
  • @Ether, the languages differ. –  Oct 11 '10 at 04:00

7 Answers7

6

If you don't want to use a tee, before you write the data, write it to a file, then send it to stdout.

You should write a logging function that does this for you to make it cleaner.

Byron Whitlock
  • 52,691
  • 28
  • 123
  • 168
3

You can do this entirely within your program, but you will still need to use anonymous pipes as created by the pipe() system call.

Basically, you will need a subprocess that performs the equivalent of tee, which is quite easy:

int child_pipe[2];
pid_t pid_exec_child, pid_output_child;

pipe(child_pipe);

pid_exec_child = fork();
if (pid_exec_child == 0)
{
    dup2(child_pipe[1], STDOUT_FILENO);
    close(child_pipe[0]);
    close(child_pipe[1]);
    execve(/* ... */);
    _exit(127);
}

close(child_pipe[1]);

pid_output_child = fork();
if (pid_output_child == 0)
{
    /* This child just loops around, reading from the other child and writing everything
     * to both stdout and the log file. */
    int logfd = open("logfile", O_WRONLY);
    char buffer[4096];
    ssize_t nread;

    while ((nread = read(child_pipe[0], buffer, sizeof buffer) != 0)
    {
        size_t nwritten_total;
        ssize_t nwritten;

        if (nread < 0)
        {
            if (errno == EINTR)
                continue;

            perror("read");
            _exit(1);
        }

        /* Copy data to stdout */
        nwritten_total = 0;
        while (nwritten_total < nread)
        {
            nwritten = write(STDOUT_FILENO, buffer + nwritten_total, nread - nwritten_total);

            if (nwritten < 0)
            {
                if (errno == EINTR)
                    continue;

                perror("write(stdout)");
                _exit(2);
            }

            nwritten_total += nwritten;
        }

        /* Copy data to logfile */
        nwritten_total = 0;
        while (nwritten_total < nread)
        {
            nwritten = write(logfd, buffer + nwritten_total, nread - nwritten_total);

            if (nwritten < 0)
            {
                if (errno == EINTR)
                    continue;

                perror("write(logfile)");
                _exit(3);
            }

            nwritten_total += nwritten;
        }
    }
    _exit(0);
}

close(child_pipe[0]);

/* Parent continues here */

Of course, it is probably easier to just exec tee in that second child instead...

(Note that the child processes use _exit(), since they have inherited the standard I/O state from the parent).

caf
  • 233,326
  • 40
  • 323
  • 462
2

Pipe it through tee.

nmichaels
  • 49,466
  • 12
  • 107
  • 135
  • is there a way to do this without pipes or tees, using just the dup() family? –  Sep 07 '10 at 17:31
2

You can use dup2() - this link provides an example

DVK
  • 126,886
  • 32
  • 213
  • 327
  • 2
    That link provides an example where stdout is redirected to a file and then redirected back to normal stdout. The poster was after a way for a normal print statement to be written to *both* stdout and a file. – a'r Sep 07 '10 at 17:41
  • @Byron - the fork is not there for printing purposes, just part of the logic of the script that has an example. the post down the same thread has pure logic with no `fork`: http://www.linuxforums.org/forum/linux-programming-scripting/69391-redirect-stdout-file.html#post363639 – DVK Sep 07 '10 at 17:43
  • 1
    As a'r said this doesn't answer the original question of writing to _both_ stdout and file. – Antony Hatchkins Mar 16 '13 at 17:30
1

Also, you can use fifo's. mkfifo my.fifo; in execv: program > my.fifo; and open fifo as a regular file, reading from it. This way you can have your stdout parsed, but it has minor drawbacks with shared access.

kagali-san
  • 2,964
  • 7
  • 48
  • 87
0

Do you want only the output of the child to go to the log?

The tee unix command does exactly what you describe: you pipe in data and it writes to a log and to stdout.

idbrii
  • 10,975
  • 5
  • 66
  • 107
0

Use Tee like this:

./myprog | tee outputfile
Taylor Leese
  • 51,004
  • 28
  • 112
  • 141