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