为什么在重定向到同一文件时会覆盖部分输出,但在重定向到终端时却不会?

Why are parts of the output overwritten when redirected to the same file but not when to terminal?

我希望能够同时输出到 stdout(&stderr) 和另一个文件(日志文件),而不用担心 shell 重定向 stdout(and/or stderr) 到同一个日志文件。

对于我的特殊情况,我尝试检查标准输出和日志文件的统计位 (man 2 stat),以确定它们指向相同的设备和 inode,在这种情况下不要 fopen() 日志文件,而是 fopen() stdout 文件,用于写入日志文件。 正是我的意思(.c 代码):https://github.com/libcheck/check/issues/188#issuecomment-492852881 这是一种解决方法。

这是一个 .c 代码示例:

#include <stdio.h>

int main() {
  FILE *f=NULL;
  f = fopen("/tmp/a_out_.log", "w");
  if (NULL == f) {
    fprintf(stderr,"oopsie\n");
  } else {
    fprintf(stdout, "Something");
    fprintf(f," messy ");
    fprintf(f," jessy\n");
    fprintf(stdout, " or another\n");
    fprintf(f,"More stuff\n");
    fclose(f);                                                                                                                  
  }
}

运行 像这样(来自 bash)来查看被覆盖的输出:

$ gcc a.c && { ./a.out >/tmp/a_out_.log ; cat /tmp/a_out_.log ; }
Something or another
uff

我已经简化了 .c 代码并将其减少到 bash 行,但是功能(即乱码输出)的说明是一样的:

所有这些都显示正确的输出:

(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2)
(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/tmp/good 2>&1 ; cat /tmp/good
(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/dev/stdout 2>/dev/stdout
(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/proc/self/fd/1 2>/proc/self/fd/1
(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/proc/self/fd/2 2>/proc/self/fd/2

输出为:

1 2 3 4 5 6 7 8 9 10
blah

但是,下一个显示了被覆盖的输出:

(echo "1 2 3 4 5 6 7 8 9 10" ; echo "blah" >&2) 1>/tmp/bad 2>/tmp/bad; cat /tmp/bad

(损坏的)输出如下:

blah
 4 5 6 7 8 9 10

发生这种情况的真实世界示例(甚至包括复制步骤):https://github.com/libcheck/check/issues/188

Why are parts of the output overwritten when redirected to the same file but not when on terminal?

因为您在普通文件上以普通写入模式分别打开文件两次。该文件的每个打开文件描述都有自己的当前文件位置,这就是它写入的数据所在的位置。每个文件位置仅根据通过相应的打开文件描述写入的数据前进。在给定位置恰好第二个写入的数据将替换第一个写入的数据。

终端不会发生这种情况,因为终端不可搜索。就好像它们总是以追加模式打开一样。以附加模式打开您的日志文件将提供一半的解决方案,并且在任何情况下都是一个好主意:

#include <stdio.h>

int main() {
  FILE *f=NULL;
  f = fopen("/tmp/a_out_.log", "a");  // <-- here is the change
  if (NULL == f) {
    fprintf(stderr,"oopsie\n");
  } else {
    fprintf(stdout, "Something");
    fprintf(f," messy ");
    fprintf(f," jessy\n");
    fprintf(stdout, " or another\n");
    fprintf(f,"More stuff\n");
    fclose(f);                                                                                                                  
  }
}

这样,写入文件 f 总是会到达文件的当前末尾,而不管其他方式可能对文件做了什么。但是,如果您希望截断现有日志文件,则必须自己执行此操作,这与以写入 ("w") 模式打开时不同。

然而,正如我所说,尽管无论如何在这种情况下以追加模式打开可能是个好主意,但这只是解决方案的一半。如果标准输出在同一个文件上单独打开,以常规写入模式,那么从那个方向写入仍然可以并且将会覆盖其他输出。坦率地说,我倾向于说 这不应该是您的程序所关心的 。如果用户 真的 想要将程序的控制台输出重定向到它的日志文件,那么他们可以在追加模式下通过使用 >> 重定向运算符来做到这一点> 个。这将构成上述一半解决方案的补充。如果他们使用 > 重定向,那是他们的责任,我不会采取非常措施来检测或容纳它。