Как перенаправить вывод обратно на экран после freopen ("out.txt", "a", stdout) - PullRequest
28 голосов
/ 15 декабря 2009
#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;
}

Я вызываю freopen для перенаправления stdout в out.txt, затем печатаю что-то в файле, теперь хочу перенаправить его обратно на экран, но freopen ("/ dev / stdout "," a ", stdout); не работает. Есть ли способ сделать это с помощью системных вызовов ANSI C или POSIX?

Ответы [ 6 ]

21 голосов
/ 15 декабря 2009

Я не могу придумать, как сделать это кроссплатформенным способом, но в системах GNU / Linux (и, возможно, в других POSIX-совместимых системах) вы можете freopen ("/dev/tty", "a", stdout). Это то, что вы пытались сделать?

21 голосов
/ 15 декабря 2009

К сожалению, нет хорошего пути:

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

Лучшая рекомендация - не использовать freopen в этих обстоятельствах.

14 голосов
/ 15 декабря 2009

Используйте fdopen() и dup(), а также 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

Я не уверен, что вы можете выполнить поручение надежно в другом месте.

Сомнительный код, который действительно работает

Приведенный ниже код работал на Solaris 10 и MacOS X 10.6.2, но я не уверен, что он надежен. Назначение структуры может работать или не работать с Linux glibc.

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

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

    return 0;
}

Вы не можете сказать, что вас не предупредили - это игра с огнем!

Если вы работаете в системе с файловой системой /dev/fd, вы можете создать имя файла, подразумеваемое дескриптором файла, возвращенного из dup() с sprintf(buffer, "/dev/fd/%d", old_stdout), а затем использовать freopen() с этим именем. Это будет намного надежнее, чем присвоение, используемое в этом коде.

В лучших решениях либо код использует fprintf (fp, ...) везде, либо использует функцию cover, которая позволяет вам установить собственный указатель файла по умолчанию:

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

Очевидно, что вы можете создавать mvprintf () и другие функции по мере необходимости.

Пример использования mprintf ()

Тогда вместо исходного кода вы можете использовать:

#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;
}

(Предупреждение: непроверенный код - слишком высокий уровень достоверности. Кроме того, весь код написан при условии, что вы используете компилятор C99, главным образом потому, что я объявляю переменные, когда они мне сначала нужны, а не в начале функции.)


Внимание:

Обратите внимание, что если исходная программа вызывается как ./original_program > file или ./original_program | grep something (с перенаправленным выводом) или запускается из задания cron, то открытие /dev/tty обычно не подходит для повторного открытия стандартного вывода потому что исходный стандартный вывод не был терминалом.

Также обратите внимание, что если перенаправление стандартного вывода используется перед разветвлением и выполнением дочерней программы, а исходный стандартный вывод восстанавливается в родительском, то последовательность операций неправильна. Вы должны форкнуть, а затем настроить ввод / вывод ребенка (только), не изменяя ввод / вывод родителя вообще.

13 голосов
/ 15 декабря 2009

Вообще говоря, вы не можете. Вы закрыли файл, который мог быть трубой или чем-то еще. Это не открывается. Возможно, вы сохранили значение stdout, затем присвойте ему значение fopen, затем закройте его и скопируйте старое значение обратно. Пример:

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

Майк Уэллер ниже предложил в комментариях, что стандартный вывод не всегда может быть доступен для записи. В этом случае может помочь что-то подобное:

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

Другое редактирование: если вы используете его для перенаправления вывода из дочернего процесса, как ваш комментарий в другом месте, вы можете перенаправить его после разветвления.

5 голосов
/ 22 марта 2014

В Windows вы можете открыть «CONOUT $».

freopen("test.txt", "w", stdout);
printf("this goes to test.txt");
freopen("CONOUT$", "w", stdout);
printf("this goes to the console\n");

Это, вероятно, не работает, если stdout перенаправлен для запуска.

1 голос
/ 24 июля 2013

Следующий код (SwapIOB) используется в Testbenches, которые хотят сохранить поток stdout для сравнения с файлом ожидаемых результатов.

Справочная информация. Управление файловыми потоками осуществляется с помощью структуры _IOB, которая хранится в массиве из 20 записей _IOB. Это включает в себя поток stdout. IOB хранятся в массиве. Когда файл создан, код приложения получает ptr для элемента в этом массиве. Затем код приложения передает этот ptr в ОС для обработки вызовов ввода-вывода. Таким образом, сама ОС НЕ содержит собственных указателей на IOB приложения или полагается на них.

Требование: при запуске тестового стенда сообщения стандартного вывода, создаваемые приложением, должны перенаправляться в файл. Однако после завершения тестируемого модуля сообщения stdout должны быть перенаправлены на консоль.

Эта процедура была протестирована и в настоящее время используется в системе Windows XP / Pro.

void SwapIOB(FILE *A, FILE *B) {

    FILE temp;

    // make a copy of IOB A (usually this is "stdout")
    memcpy(&temp, A, sizeof(struct _iobuf));

    // copy IOB B to A's location, now any output
    // sent to A is redirected thru B's IOB.
    memcpy(A, B, sizeof(struct _iobuf));

    // copy A into B, the swap is complete
    memcpy(B, &temp, sizeof(struct _iobuf));

}  // end SwapIOB;

Код приложения использует SwapIOB () аналогично:

FILE *fp;

fp = fopen("X", "w");

SwapIOB(stdout, fp);

printf("text to file X");

SwapIOB(stdout, fp);

fclose(fp);

printf("text to console works, again!");
...