You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Bash脚本如何实时读取子进程stdout 无需等待进程终止

问题背景

现有一个C++编写的计数程序,逻辑为从0开始计数,每秒数值加1,通过printf向标准输出打印当前计数值,程序源码如下:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
        int ctr = 0;

        for (int i = 0; i < 10; ++i)
        {
                printf("%i\n", ctr++);
                sleep(1);
        }

        return 0;
}

需求是通过Bash脚本调用该程序,每次程序输出新的计数值时立刻执行对应处理逻辑,且不能修改C++程序源码,仅可调整Bash脚本实现。
原有存在问题的Bash脚本如下,该脚本会等待C++程序完全执行结束后,才一次性处理所有输出值,无法满足实时处理要求:

#!/bin/bash

for c in $(./script-test)
do
    echo $c
done
故障原因

原有脚本无法实时处理输出由两个原因共同导致:

  • 命令替换语法$(命令)的固有逻辑:Shell会等待括号内的命令完全执行终止,收集完该命令的所有标准输出内容后,才会将内容拆分传入for循环遍历,天生不支持实时读取运行中进程的输出流。
  • 标准输出缓冲机制:当程序的标准输出连接的是管道/文件而非交互终端时,C标准库的printf默认会开启全缓冲模式,缓冲区攒够4k左右数据才会真正把内容写入管道,哪怕输出内容带换行符也不会立刻刷新缓冲区,进一步加剧了输出延迟。
可行实现方案

通过系统自带的流缓冲修改工具+管道逐行读逻辑即可实现实时输出处理,无需修改C++代码,调整后的Bash脚本如下:

#!/bin/bash

# 将目标程序的标准输出设为行缓冲,通过管道逐行实时读取输出
stdbuf -oL ./script-test | while IFS= read -r line
do
    # 此处替换为实际需要执行的处理逻辑,示例为打印读取到的值
    echo "$line"
done

关键参数说明

  • stdbuf -oL:是GNU coreutils自带的标准工具,可在不修改程序源码的前提下,调整启动程序的流缓冲策略;参数-oL表示将标准输出(stdout)设置为行缓冲模式,即每次输出遇到换行符就立刻刷新缓冲区将内容写入管道,完美解决printf默认全缓冲导致的输出不实时问题。
  • while IFS= read -r line:是Bash中逐行读取管道流的标准写法,管道传入的内容每到一行就会立刻触发循环体执行,不会等待上游程序终止;IFS=用于保留行首尾的空白字符,-r参数用于禁止自动转义反斜杠字符,避免读取内容被意外修改。

极端场景兼容方案

如果遇到极少数静态编译、不支持stdbuf注入缓冲配置的程序,可以换用unbuffer工具实现相同效果,该工具随expect包提供,脚本写法如下:

#!/bin/bash
# 需提前安装expect包:Debian/Ubuntu执行apt install expect,RHEL/CentOS执行yum install expect
unbuffer ./script-test | while IFS= read -r line
do
    echo "$line"
done

内容的提问来源于stack exchange,提问作者Stuart Bailey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 03:57:22