[comp.unix.questions] Redirection of stderr

jik@athena.mit.edu (Jonathan I. Kamens) (03/15/91)

In article <574@dprmpt.UUCP>, larry@dprmpt.UUCP (Larry) writes:
|> In a C program, how can I change stderr from wherever it is directed
|> via the command line to a file, then later revert it back to it's 
|> original destination?

  This is not a comp.unix.wizards question.  I have cross-posted this response
to comp.unix.questions and directed followups there.

  First, "stderr_fd = dup(fileno(stderr))" to preserve the original stderr
output direction.  Then "fclose(stderr)", and immediately do "stderr =
fopen(your_stderr_file, "w")" (the "immediately" is so that the newly opened
file will get the same file descriptor stderr had, in case you've got that
file descriptor hard-coded somewhere in your code).  When you're done,
"fclose(stderr)" and do "stderr = fdopen(stderr_fd, "w")" to put things back
the way they were originally.

-- 
Jonathan Kamens			              USnail:
MIT Project Athena				11 Ashford Terrace
jik@Athena.MIT.EDU				Allston, MA  02134
Office: 617-253-8085			      Home: 617-782-0710

jik@athena.mit.edu (Jonathan I. Kamens) (03/15/91)

  Someone has pointed out to me in E-mail that assigning to stderr as I
indicated in my previous message won't work on many systems because of the way
stderr is defined.

  There are ways to get around this.  One of them, a rather gross method that
will nonetheless work on many systems, is to assign to stderr like this:

    *(stderr) = *(value to assign)

e.g.

    *(stderr) = *(fopen(error_file, "w"));

  However, this won't work if FILE * is an opaque type whose contents are not
spelled out for the compiler in <stdio.h>.

  It is possible to replace stderr with a file doing something like this:

    freopen(error_file, "w", stderr);

However, if you do things this way, I can't see any way to put the original
stderr back in place -- there is no "fdreopen".  That's unfortunate.

-- 
Jonathan Kamens			              USnail:
MIT Project Athena				11 Ashford Terrace
jik@Athena.MIT.EDU				Allston, MA  02134
Office: 617-253-8085			      Home: 617-782-0710

jik@athena.mit.edu (Jonathan I. Kamens) (03/15/91)

  Another thing to mention about this question occurred to me in the shower
this morning (really! :-).

  Why do you need to redirect stderr temporarily?  It seems to me that there
are two possible explanations for this:

  1) You want the error messages your program prints to go somewhere else
temporarily.

  2) You want to capture the error messages printed by a process invoked by
your program.

  If the reason is (1), then a cleaner way to do what you're trying to do
would be to keep a global FILE * variable in your namespace to which your
program prints errors, and then change that variable when you want to redirect
errors elsewhere.  In other words, the routine that replaces stderr with a
file would do something like this (you add the error checking):

    extern FILE *error_file;
    extern char *error_file_name;

    error_file = fopen(error_file_name, "w");

and the routing that puts stderr back would simply do this:

    error_file = stderr;

and then all of your routines that print errors would print to error_file
instead of printed to stderr explicitly.

  If the reason is (2), then once again there is a cleaner way to do this. 
Obviously, you have to fork in order to run a new process if you expect
control to return to your process (and if you don't expect control to return
to your process, there's no reason for you to have to put stderr back, so the
question is moot).  After the fork, simply freopen the error file on top of
stderr in the child that runs the subprocess.  The parent will get to keep the
original stderr, and the child's errors will go to the file.

  If you're using popen() or system() or something right now, then you'll
either have to write your own version of popen() or system() that takes a file
name to put errors in and does the freopen after the fork, or you can put a
wrapper around the standard functions to be a bit clever about doing things so
that you don't have to roll your own.

-- 
Jonathan Kamens			              USnail:
MIT Project Athena				11 Ashford Terrace
jik@Athena.MIT.EDU				Allston, MA  02134
Office: 617-253-8085			      Home: 617-782-0710

tb@sequent.sequent.com (Tony Booker) (03/15/91)

In article <1991Mar14.225058.13507@athena.mit.edu> jik@athena.mit.edu (Jonathan I. Kamens) writes:

     It is possible to replace stderr with a file doing something like this:

       freopen(error_file, "w", stderr);

   However, if you do things this way, I can't see any way to put the
   original stderr back in place -- there is no "fdreopen".  That's
   unfortunate.

I beleive the return value of freopen() is the original file for you
to salt away as necessary.
--

The above drivel is mine.  Company drivel sounds _MUCH_ more plausible ;^)

jik@athena.mit.edu (Jonathan I. Kamens) (03/18/91)

In article <TB.91Mar15072648@sequent.sequent.com>, tb@sequent.sequent.com (Tony Booker) writes:
|> In article <1991Mar14.225058.13507@athena.mit.edu> jik@athena.mit.edu (Jonathan I. Kamens) writes:
|>    However, if you do things this way, I can't see any way to put the
|>    original stderr back in place -- there is no "fdreopen".  That's
|>    unfortunate.
|> 
|> I beleive the return value of freopen() is the original file for you
|> to salt away as necessary.

From freopen(3):

     Freopen substitutes the named file in place of the open
     stream.  It returns the original value of stream.  The ori-
     ginal stream is closed.

In other words, yes, it returns the original stream, but it's closed and
replaced by the new file, so you can't do anything useful with it.  To verify
this, compile and run the following program.  If what you say works, then the
second fprintf should cause output to go to the tty; if not, the second
fprintf should appear in the testfile.  On my system, it does.

#include <stdio.h>

#define TESTFILE "/tmp/freopen-testfile"

main()
{
     FILE *old;

     old = freopen(TESTFILE, "w", stdout);

     fprintf(stdout, "This is being written to stdout, now %s.\n",
	     TESTFILE);
     fprintf(old, "This is being written to the old stdout.\n");

     fclose(stdout);
     fclose(old);
     
     exit(0);
}

-- 
Jonathan Kamens			              USnail:
MIT Project Athena				11 Ashford Terrace
jik@Athena.MIT.EDU				Allston, MA  02134
Office: 617-253-8085			      Home: 617-782-0710

P.S. It's usually a good idea to actually try suggestions like this out before
posting them to the net.  Yes, I know, I'm not one to talk, considering that I
posted about assigning to stderr when that won't work on most systems anyway. 
But I thought I'd point it out anyway. :-)