【问题标题】:Writing to two file descriptors by only one operation仅通过一个操作写入两个文件描述符
【发布时间】:2013-03-30 08:40:25
【问题描述】:

我想在 C 中实现日志记录功能并将消息记录到标准输出和某些文件。 我想写类似 fprintf(logout, "msg");以某种方式声明的 FILE* logout 会将字符串重定向到标准输出和某个文件。是否可以?

【问题讨论】:

  • 创建自己的函数,输出到文件和标准输出。
  • 我有 dump_MY_OWN_TYPE(TYPE) 之类的函数,并希望将其保留为 dump_MY_OWN_TYPE(TYPE, FILE* f) 所以我可以称它们为 dump_MY_OWN_TYPE(TYPE, stdout) 或 dump_MY_OWN_TYPE(TYPE, logout)

标签: c logging file-descriptor


【解决方案1】:

如果这是在 Linux 上,您可以打开一个指向tee 命令的管道:

FILE *logout = popen("tee logfile", "w");

【讨论】:

  • 这或多或少是我使用的,但有一个问题可能会或可能不重要,具体取决于您的用例:如果您从命令行运行程序,则子进程(您的tee 程序)不一定会在正常情况下刷新,更有趣的是,当您的程序完成时,tee 会短暂保持打开状态,这意味着您不会得到提示返回 quit像您通常那样(tee 仍然会在屏幕上写入一段时间,这意味着您的提示会隐藏在文本中)。总而言之,这种方法“非常”有效,而且非常简单,所以......
  • @scott_fakename 我希望tee 使用stdio,并使用默认缓冲。因此,如果它的标准输出(它从您的进程继承)进入终端,它将被行缓冲;如果 stdout 被重定向到文件或管道,它将被完全缓冲。而且我看不出为什么tee 在其输入管道上获得EOF 后会继续运行很多;当它读取 EOF 时,它应该立即刷新所有缓冲区并退出。
  • 我想我应该澄清一下;当我这样做时,我做了一个 dup2 以使我的程序的标准输出打印到tee 进程,当程序关闭时,我描述的结果发生了。我没有花太多心思去研究它的确切机制,但这就是发生的事情。
  • 我不确定为什么会有所不同,但我们必须查看整个程序才能确定。 tee 除了将其输入复制到所有输出之外,什么都不做,一旦它在输入上收到 EOF,它应该刷新并关闭所有输出并退出。
【解决方案2】:

您显然想要一个类似FILE 的对象,该对象将其写入重定向到两个底层FILEs(stdout 和日志文件)。标准 C 不允许以任何方式“子类化”FILE 对象,因此这在 C 中是不可能的。但是,GNU libc does,所以如果您的程序仅适用于 Linux,您可以通过一些编程来做到这一点。由于这非常不便携,因此强烈建议不要这样做。

实现这一点的一种更便携的方法是写入管道并创建一个进程或线程,该进程或线程从管道读取并写入两个底层文件。例如,假设 POSIX:

FILE *tee_file(FILE *fp1, FILE *fp2)
{
  int fds[2];
  if (pipe(fds))
    return NULL;
  switch (fork()) {
    case 0: {
      char buf[512];
      int nread;
      FILE *r = fdopen(fds[0], "r");
      close(fds[1]);
      while ((nread = fread(buf, 1, sizeof buf, r)) {
        fwrite(buf, 1, nread, fp1);
        fwrite(buf, 1, nread, fp2);
      }
      _exit(0);
    }
    case -1:
      return NULL;
  }
  close(fds[0]);
  return fdopen(fds[1], "w");
}

/* after calling "fp = tee_file(fp1, fp2)", writes to fp
   end up in fp1 and fp2. */

这两种方法都给您的程序增加了很多复杂性,这应该是有充分理由的。您想要的是使用一个允许写入多个输出的日志框架(它们都可以),或者按照 H2CO3 的答案编写自己的。

【讨论】:

  • 谢谢。所以标准 C 库不允许这样做,我需要摆脱这种想法。
  • @DennisYurichev 您可以在 Unix 上使用单独的线程或进程执行此操作,请参阅编辑后的答案,但这仍然不是一个好主意,除非您有一个 真正 好的理由(例如支持仅接受 stdio 对象作为参数的第 3 方库)。
【解决方案3】:

为什么不制作自己的日志功能?

int log_fprintf(FILE *stream, const char *restrict fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(stream, fmt, args);
    va_end(args);

    va_start(args, fmt);
    int n = vfprintf(stdout, fmt, args);
    // conceptually, rather `stderr`, please!
    va_end(args);

    return n;
}

【讨论】:

  • 因为我有 dump_MY_OWN_TYPE(TYPE) 之类的功能,并希望将其保留为 dump_MY_OWN_TYPE(TYPE, FILE* f) 所以我可以称它们为 dump_MY_OWN_TYPE(TYPE, stdout) 或 dump_MY_OWN_TYPE(TYPE, logout )
  • 想象一下复杂的数据类型:struct cplx { float re;浮动即时消息; };想象一下 dumper 函数接受这两个字段并将它们放入 FILE:dump_cplx (struct cplx*, FILE*)
  • @DennisYurichev 为此,如果没有必要,编写自己的函数特别有用。 printf() 不会为您转储自定义结构。
猜你喜欢
  • 1970-01-01
  • 2017-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-18
  • 2019-06-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多