为什么程序输出重定向会使其子流程的输出出现故障?

时间:2013-07-03 09:37:26

标签: c subprocess buffering io-redirection

在我的C程序中,在Linux上运行,使用system()创建子进程我注意到当我将 stdout 重定向到管道或文件时,然后输出子进程在缓冲I / O函数(如printf())的输出之前发送。当 stdout 离开去终端时,输出按预期顺序排列。我将程序简化为以下示例:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    printf("1. output from printf()\n");
    system("echo '2. output from a command called using system()'");

    return EXIT_SUCCESS;
}

stdout 进入终端时的预期输出:

$ ./iobuffer
1. output from printf()
2. output from a command called using system()

stdout 重定向到管道或文件时输出乱序:

$ ./iobuffer | cat
2. output from a command called using system()
1. output from printf()

2 个答案:

答案 0 :(得分:4)

终端通常使用行缓冲,而管道则使用块缓冲。

这意味着您的printf调用(包括换行符)将填充行缓冲区,触发刷新。重定向时,在程序完成之前不会进行刷新。

另一方面,

echo总是在 完成时刷新它正在写入的缓冲区。

使用行缓冲(终端输出),顺序为:

  • printf()使用换行符打印一行,刷新缓冲区,您会看到1. output from printf()正在打印。
  • echo写入输出,退出,刷新缓冲区,您看到2. output from a command called using system()已打印。

使用块缓冲,顺序为:

  • printf()使用换行符打印一行,而不是完全填充缓冲区。
  • echo写入输出,退出,刷新缓冲区,您看到2. output from a command called using system()已打印。
  • 您的程序退出,刷新其块缓冲区,您会看到1. output from printf()正在打印。

您可以选择使用fflush()明确刷新,或使用stdout明确设置setvbuf()上的缓冲。

答案 1 :(得分:2)

此回复补充了Martijn Pieters的回复。描述流的默认缓冲模式的源代码的引用最后。

解决方案

以下是三个基本解决方案的解释:

a)在调用子进程之前刷新缓冲区。当您从主程序执行大量输出并且只进行少量子进程调用时,此选项可能会更加有效。

printf("1. output from printf()\n");
fflush(stdout);
system("echo '2. output from a command called using system()'");

b)将整个程序的 stdout 的缓冲更改为行缓冲(或无缓冲)。此选项只是对程序的一个小改动,因为您只在开始时调用sevbuf(),而程序的其余部分保持不变。

if(setvbuf(stdin, NULL, _IOLBF, BUFSIZ))
    err(EXIT_FAILURE, NULL);
printf("1. output from printf()\n");
system("echo '2. output from a command called using system()'");

修改
c)通过外部实用程序将整个程序的 stdout 的缓冲更改为行缓冲(或无缓冲)。此选项根本不会更改程序,因此您无需重新编译或甚至拥有程序源。您只需使用stdbuf实用程序调用该程序。

$ stdbuf -oL ./iobuffer | cat
1. output from printf()
2. output from a command called using system()

参考 - 描述缓冲模式改变的原因

例如,在下面的文档中描述了初始缓冲设置。默认情况下,对终端等交互式设备的流进行行缓冲,以便在终端上立即显示换行结束消息。管道,文件等使用块缓冲(或完全缓冲)以获得更好的性能。

GNU C Library参考手册
http://www.gnu.org/software/libc/manual/html_node/Buffering-Concepts.html#Buffering-Concepts

  

新打开的流通常是完全缓冲的,但有一个例外:   连接到诸如终端的交互设备的流是   最初是行缓冲的。

Linux man-pages:stdin(3)
http://linux.die.net/man/3/stdin

  

流stderr是无缓冲的。流标准输出是行缓冲的   当它指向一个终端。部分线将不会出现   调用fflush(3)或exit(3),或打印换行符。这个可以   产生意外的结果,特别是调试输出。该   标准流(或任何其他流)的缓冲模式可以是   使用setbuf(3)或setvbuf(3)调用更改。

还提到了终端驱动程序的缓冲。

ISO / IEC 9899:201x C11委员会草案 - 2011年4月12日; 7.21.3文件,第301页
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

  

最初打开时,标准错误流未完全缓冲;   标准输入和标准输出流是完全缓冲的,如果   并且只有当流可以被确定为不参考时   互动设备。

开放组:系统接口和标题第4版,第2版; 2.4标准I / O流,第32页
https://www2.opengroup.org/ogsys/catalog/C435(需要免费注册下载)

  

打开时,标准   错误流未完全缓冲;标准输入和标准   当且仅当流可以时,输出流才完全缓冲   决定不参考互动设备。

还有一个非常有趣的章节2.4.1“文件描述符和标准I / O流的交互”关于缓冲和非缓冲I / O的组合,这有点与子进程调用有关。