Как закрыть stdout и stderr в C? - PullRequest
       15

Как закрыть stdout и stderr в C?

11 голосов
/ 11 февраля 2011

Мне нужно закрыть stdout и stderr для одной из моих программ на Си. Как это возможно, не выходя из программы в процессе выполнения?

Ответы [ 5 ]

45 голосов
/ 11 февраля 2011

Вы можете просто:

fclose(stdout);
fclose(stderr);

Для всех, кто задается вопросом, почему вы можете захотеть сделать это, это довольно распространенная задача для процесса демона / службы в Unix.

Однако вы должны знать, что закрытие дескриптора файла может иметь непредвиденные последствия:

  • Когда вы открываете новые файлы, будут использоваться эти теперь бесплатные дескрипторы.Так, например, если вы впоследствии fopen, этот файловый дескриптор (по крайней мере в Linux) заменит fd 1, то есть stdout.Любой код, который впоследствии использует это, будет записывать в этот файл, что может отличаться от того, что вы намеревались.
  • См. Комментарии R .. относительно файловых дескрипторов по сравнению с указателями в библиотеке C FILE*.В частности:
    • Если вы пишете на закрытый fd под Linux, вы получите ошибку, но:
    • Если вы используете функцию библиотеки C, которая использует stdout или stderr (которые являются FILE* указателями (см. их определение ), а запись в них, в то время как FILE* закрыто, является неопределенным поведением. Это, вероятно, приведет к аварийному завершению работы вашей программы, не всегда в точке ошибки.См. неопределенное поведение .
  • Ваш код затрагивает не только одну часть. Любые используемые вами библиотеки и любые запускаемые вами процессы, которые унаследовали эти файловые дескрипторы как ихтакже затрагиваются стандартные дескрипторы.

Быстрое решение, состоящее из одной строки: freopen() Скажем /dev/null, /dev/console под Linux / OSX или nulв Windows. В качестве альтернативы вы можете использовать реализацию, специфичную для вашей платформы, чтобы при необходимости повторно открыть дескрипторы файлов / дескрипторы.

8 голосов
/ 11 февраля 2011

Что вы пробовали? fclose не работает?

2 голосов
/ 03 мая 2016

Предупреждение: я совсем не знаком с C, но недавно прочитал слайд, на который напрямую отвечает Джим Мейринг, сотрудник RedHat и сопровождающий GNUlib: https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodbye-world.pdf. Я просто суммирую.

TL; DR

Получите closeout.c и его зависимости от GNUlib в ваш источник и вызовите

atexit(close_stdout);

как ваша первая строка в main.

Краткое описание

Сначала несколько предупреждений, цитируя POSIX :

Поскольку после вызова fclose () любое использование потока приводит к неопределенному поведению, fclose () не следует использовать в stdin, stdout или stderr, за исключением непосредственно перед завершением процесса, ...... Если есть какие-либо Обработчики atexit (), зарегистрированные приложением, такого вызова fclose () не должны происходить, пока не завершится последний обработчик. После того как fclose () была использована для закрытия stdin, stdout или stderr, стандартного способа повторно открыть любой из этих потоков не существует.

За использованием close () в файловых дескрипторах STDIN_FILENO, STDOUT_FILENO или STDERR_FILENO следует немедленно выполнить операцию для повторного открытия этих файловых дескрипторов. ...... Кроме того, close (), за которым следует операция повторного открытия (например, open (), dup () и т. Д.), Не является атомарной; dup2 () должен использоваться для изменения стандартных файловых дескрипторов.

Закрытие потока без обработки его ошибок не является надежным, и то же самое для stdout и stderr. Вот список ошибок, которые вам нужно устранить:

  • fclose(stdout)
  • ferror(stdout) a.k.a. предыдущая ошибка
  • __fpending(stdout) a.k.a. материал не очищен

Обработка этих ошибок, как GNUlib реализует в close-stream.c , приведена ниже.

int
close_stream (FILE *stream)
{
  const bool some_pending = (__fpending (stream) != 0);
  const bool prev_fail = (ferror (stream) != 0);
  const bool fclose_fail = (fclose (stream) != 0);

  /* Return an error indication if there was a previous failure or if
     fclose failed, with one exception: ignore an fclose failure if
     there was no previous error, no data remains to be flushed, and
     fclose failed with EBADF.  That can happen when a program like cp
     is invoked like this 'cp a b >&-' (i.e., with standard output
     closed) and doesn't generate any output (hence no previous error
     and nothing to be flushed).  */

  if (prev_fail || (fclose_fail && (some_pending || errno != EBADF)))
    {
      if (! fclose_fail)
        errno = 0;
      return EOF;
    }

  return 0;
}

Примечание: __fpending специально для glibc и может не быть переносимым. OTOH, это на пути к стандартизации как fpending.

P.S:.

Я просто хотел направить вывод stdout и stderr в файл журнала вместо консоли.

Это не очень хорошая причина закрывать stdout и stderr, если вы пишете демон в соответствии с http://cloud9.hedgee.com./scribbles/daemon#logging. Вы должны позволить менеджеру демона (например, инструменты демона, runit, s6, nosh, OpenRC и systemd) справиться с перенаправлением.

Тем не менее, вы все равно должны закрыть любой поток, в который программа когда-либо записывала в конце, чтобы проверить ошибки. Цитата из close-stream.c:

Если программа записывает что-нибудь в STREAM, эта программа должна закрыться ПОТОК и убедитесь, что это успешно, прежде чем выйти. Иначе, Предположим, что вы идете до крайности проверки статуса возврата каждой функции, которая делает явную запись в STREAM. Последний printf может преуспеть в записи во внутренний буфер потока, и все же fclose (STREAM) может все еще потерпеть неудачу (например, из-за ошибки переполнения диска) когда он пытается записать эти буферизованные данные. Таким образом, вы бы осталось с неполным выходным файлом и программа-нарушитель выйти успешно. Даже вызова fflush не всегда достаточно, поскольку некоторые файловые системы (NFS и CODA) буферизуют записанные / очищенные данные до фактического закрытия вызова.

Кроме того, расточительно проверять возвращаемое значение при каждом вызове что пишет в STREAM - просто пусть запись состояния внутреннего потока провал. Это то, что тест на ужасы проверяет ниже.

2 голосов
/ 11 февраля 2011

Если вы хотите запретить приложению записывать данные на консоль, то:

#include <stdio.h>

int main()
{    
    fprintf(stdout, "stdout: msg1\n");
    fprintf(stderr, "stderr: msg1\n");
    fclose(stdout);

    fprintf(stdout, "stdout: msg2\n");  // Please read the note setion below
    fprintf(stderr, "stderr: msg2\n");
    fclose(stderr);

    fprintf(stdout, "stdout: msg3\n");
    fprintf(stderr, "stderr: msg3\n");
}

Выходы:

stdout: msg1
stderr: msg1
stderr: msg2

Примечание: любая попытка использоватьУказатель FILE после закрытия файла является ошибочным.В этом случае я делаю это только для иллюстрации того, что закрытие этих файловых дескрипторов может сделать для вашего приложения.

0 голосов
/ 22 апреля 2019

На самом деле вы также можете использовать функцию close:

#include<unistd.h> // close
#include<stdio.h>  // STDOUT_FILENO,STDERR_FILENO
...
close(STDOUT_FILENO);
close(STDERR_FILENO);
...
...