tags:

views:

659

answers:

4
#include <stdio.h>

int main() {
    printf("This goes to screen\n");
    freopen("out.txt", "a", stdout);
    printf("This goes to out.txt");
    freopen("/dev/stdout", "a", stdout);
    printf("This should go to screen too, but doesn't\n");

    return 0;
}

I call freopen to redirect the stdout to out.txt then I print something on the file, now I want to redirect it back to the screen, but freopen("/dev/stdout", "a", stdout); doesn't work. Is there any way to do that using ANSI C or POSIX system calls?

+6  A: 

Unfortunately, there doesn't seem to be a good way:

http://c-faq.com/stdio/undofreopen.html

The best recommendation is not to use freopen in this circumstance.

Andy West
+2  A: 

Generally speaking, you can't. You have closed the file, which could've been pipe or whatever. It's not reopenable. You might have saved stdout value, then assign some fopen to it and then close it and copy the old value back. Example:

FILE *o = stdout;
stdout=fopen("/tmp/crap.txt","a");
printf("Oh no!\n");
fclose(stdout);
stdout = o;

Mike Weller suggested below in comments that stdout might not always be writable. In this case something like that might help:

int o = dup(fileno(stdout));
freopen=fopen("/tmp/crap.txt","a",stdout);
printf("Oh no!\n");
dup2(o,fileno(stdout));
close(o);

Another edit: if you're using it to redirect output from the child process like your comment elsewhere suggest, you can redirect it after the fork.

Michael Krelin - hacker
Except stdio probably won't be writable in a reliable way under some implementations.
Mike Weller
Mike Weller, could be. I've tried to provide alternative solution.
Michael Krelin - hacker
+1  A: 

I can't think of a way to do this in a cross-platform manner, but on GNU/Linux systems (and maybe other POSIX-compliant ones, too) you can freopen ("/dev/tty", "a", stdout). Is this what you were trying to do?

Patrick Niedzielski
This works and is good enough for me, thanks
Hoffmann
This will break things in case of redirection or piping to another process.
Michael Krelin - hacker
Only the last process that I fork() needs to redirect to the screen. So this works for my case.
Hoffmann
Hoffman, of course it may be good enough for your particular case, so I'm putting this comments for those who will find the question and may care about the case when the whole thing is redirected or has no tty at all.
Michael Krelin - hacker
I agree, hacker. I like Jonathan Leffler's solution below, in which we has a wrapper for printf and a default stream. A very nice solution, and cross platform (as far as I can tell), too.
Patrick Niedzielski
+3  A: 

Use fdopen() and dup() as well as freopen().

int old_stdout = dup(1);  // Preserve original file descriptor for stdout.

FILE *fp1 = freopen("out.txt", "w", stdout);  // Open new stdout

...write to stdout...   // Use new stdout

FILE *fp2 = fdopen(old_stdout, "w");   // Open old stdout as a stream

...Now, how to get stdout to refer to fp2?
...Under glibc, I believe you can use:

fclose(stdout);    // Equivalent to fclose(fp1);
stdout = fp2;      // Assign fp2 to stdout
// *stdout = *fp2;   // Works on Solaris and MacOS X, might work elsewhere.

close(old_stdout);   // Close the file descriptor so pipes work sanely

I'm not sure whether you can do the assignment reliably elsewhere.

Dubious code that does actually work

The code below worked on Solaris 10 and MacOS X 10.6.2 - but I'm not confident that it is reliable. The structure assignment may or may not work with Linux glibc.

#include <stdio.h>

int main()
{
    printf("This goes to screen\n");
    int old_stdout = dup(1);
    FILE *fp1 = freopen("out.txt", "a", stdout);
    printf("This goes to out.txt\n");
    fclose(stdout);
    FILE *fp2 = fdopen(old_stdout, "w");
    *stdout = *fp2;
    printf("This should go to screen too, but doesn't\n");

    return 0;
}

You can't say you weren't warned -- this is playing with fire.

The better solutions either make the code use 'fprintf(fp, ...)' everywhere, or use a cover function that allows you set your own default file pointer:

mprintf.c

#include "mprintf.h"
#include <stdarg.h>

static FILE *default_fp = 0;

void set_default_stream(FILE *fp)
{
    default_fp = fp;
}

int mprintf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);

    if (default_fp == 0)
        default_fp = stdout;

    int rv = vfprintf(default_fp, fmt, args);

    va_end(args);
    return(rv);
 }

mprintf.h

#ifndef MPRINTF_H_INCLUDED
#define MPRINTF_H_INCLUDED

#include <stdio.h>

extern void set_default_stream(FILE *fp);
extern int  mprintf(const char *fmt, ...);

#endif

Clearly, you can create an mvprintf() and other functions as needed.

Example use of mprintf()

Then, in place of the original code, you can use:

#include "mprintf.h"

int main()
{
    mprintf("This goes to screen\n");
    FILE *fp1 = fopen("out.txt", "w");
    set_default_stream(fp1);
    mprintf("This goes to out.txt\n");
    fclose(fp1);
    set_default_stream(stdout);
    mprintf("This should go to screen too, but doesn't\n");

    return 0;
}

(Warning: untested code - confidence level too high. Also, all code written assuming you use a C99 compiler, primarily because I declare variables when I first need them, not at the beginning of the function.)

Jonathan Leffler
Your first suggestion seems to be the right way to do it, but Patrick's suggestion is a lot simpler and does what I need. I did not test your answer but I think it should work on linux too. Thanks for the answer though.fprintf(fp,...) is not an option for me because I use exec() on the child process.
Hoffmann
Hoffman, if you're using exec (presumably you first fork, if it's a child process) then why not do the redirection *after* you forked?
Michael Krelin - hacker
This is actually a really nice solution, Hoffmann. You could even simplify this down with a variable argument macro to wrap printf. This would only take one line.
Patrick Niedzielski
The second suggestion, that is.
Patrick Niedzielski