流读取exec执行的C程序与Shell脚本输出的行为异常问题
问题根因
这是标准C库的默认IO缓冲策略导致的正常行为,不属于任何组件的bug:
- 当C程序的stdout指向交互式终端时,
printf默认采用行缓冲策略,每次输出换行符就会自动刷新缓冲区到输出流 - 当通过Go的
exec.Command调用子进程时,子进程的stdout/stderr默认关联的是管道而非终端,此时C标准库会自动切换为全缓冲策略,缓冲区默认大小通常为4KB,只有缓冲区填满、程序主动调用刷新接口、或者进程退出时才会将缓冲区内容写入管道,所以会看到10秒后程序退出才一次性输出所有内容 - Bash的
echo命令默认每次执行都会主动刷新输出缓冲区,所以即使输出到管道也能实现每秒一行的流式输出
解决方案
方案1:修改C程序(最可靠)
可以任选以下两种修改方式之一:
- 每次
printf后手动刷新缓冲区:
#include <stdio.h> #include <unistd.h> int main() { int i; for (i=1; i<=10; i++) { printf("run %d\n", i); fflush(stdout); // 新增行,主动刷新stdout缓冲区 sleep(1); } return 0; }
- 程序初始化时直接将stdout设置为行缓冲模式:
#include <stdio.h> #include <unistd.h> int main() { // 程序开头设置stdout为行缓冲,第二个参数为NULL表示由标准库分配缓冲区,第三个参数_IOLBF表示行缓冲,第四个参数为缓冲区大小,传0使用默认大小 setvbuf(stdout, NULL, _IOLBF, 0); int i; for (i=1; i<=10; i++) { printf("run %d\n", i); sleep(1); } return 0; }
方案2:无需修改C程序,在调用侧使用stdbuf工具强制行缓冲
Linux系统自带的stdbuf工具可以修改指定程序的标准IO缓冲策略,不需要修改源码,Go代码调整如下:
// 将原来的cmd2定义替换为这一行 cmd2 := exec.Command("stdbuf", "-oL", "./testcommands/testcommand")
其中-oL参数表示将标准输出设置为行缓冲模式。
额外注意事项
你当前Go代码中使用io.MultiReader(stdout, stderr)的实现存在逻辑缺陷:MultiReader会先完全读取第一个输入流(stdout)的所有内容,才会开始读取第二个输入流(stderr),如果子进程有输出到stderr的内容,会被阻塞到stdout完全关闭后才会被读取,不符合实时流式输出的预期。建议改为启动两个goroutine分别读取stdout和stderr。
内容的提问来源于stack exchange,提问作者COMMANDER CAPSLOCK
相关产品推荐
相关产品推荐

