Go语言使用exec.CommandContext逐行捕获程序输出并同步到os.Stdout求助
Go exec.CommandContext实时读取子进程逐行输出解决方案
问题根源
你的代码有两个核心错误导致无法获取子进程输出:
- 第一次尝试扫描
os.Stdin是完全错误的方向:os.Stdin是当前Go程序的标准输入,和你启动的子进程mydaemon的输出没有任何关系 - 第二次尝试扫描
bytes.Buffer不满足实时读取的要求:bufio.Scanner读取bytes.Buffer时,如果当前缓冲区没有数据,Scan()会直接返回false退出循环,不会等待后续通过MultiWriter写入缓冲区的新内容,自然不会进入循环体执行日志打印逻辑。
解决方案
使用exec.Cmd自带的StdoutPipe()、StderrPipe()获取子进程输出的可读流,在单独的goroutine中逐行扫描流内容,同时将读到的内容写入os.Stdout/os.Stderr满足你原有打印到控制台的需求,同时将行内容发送到自定义通道做后续正则匹配。
可运行的代码示例
func (d *Daemon) Start() { ctx, cancel := context.WithCancel(context.Background()) d.cancel = cancel go func() { args := "-x f -a 1" cmd := exec.CommandContext(ctx, "mydaemon", strings.Split(args, " ")...) // 提前获取子进程的输出管道 stdoutPipe, err := cmd.StdoutPipe() if err != nil { log.Fatal(err) } stderrPipe, err := cmd.StderrPipe() if err != nil { log.Fatal(err) } lines := make(chan string, 10) // 加小缓冲避免写入阻塞 // 单独处理标准输出流 go func() { scanner := bufio.NewScanner(stdoutPipe) for scanner.Scan() { line := scanner.Text() fmt.Fprintln(os.Stdout, line) // 输出到系统标准输出,替代原有MultiWriter功能 lines <- line } if err := scanner.Err(); err != nil { log.Printf("读取stdout错误: %v", err) } }() // 单独处理标准错误流 go func() { scanner := bufio.NewScanner(stderrPipe) for scanner.Scan() { line := scanner.Text() fmt.Fprintln(os.Stderr, line) // 输出到系统标准错误 lines <- line } if err := scanner.Err(); err != nil { log.Printf("读取stderr错误: %v", err) } }() err = cmd.Start() if err != nil { log.Fatal(err) } // 后台等待子进程退出,回收系统资源 go func() { if err := cmd.Wait(); err != nil { log.Printf("子进程退出: %v", err) } close(lines) // 子进程退出后关闭通道,通知消费方输出结束 }() // 循环监听输出行和取消信号 for { select { case outputx, ok := <-lines: if !ok { // 通道关闭说明子进程已退出,直接结束循环 return } // 此处可添加你的正则匹配逻辑,修改内部状态 fmt.Println("拿到输出行:", outputx) case <-ctx.Done(): log.Println("收到取消信号,退出") return } } }() }
补充说明
如果子进程的输出存在延迟(不是实时打印),大概率是子进程自身的输出缓冲策略导致(比如默认全缓冲,只有缓冲满了才输出内容),这种情况可以通过伪终端(PTY)启动子进程强制行缓冲输出,即可拿到实时内容。
内容的提问来源于stack exchange,提问作者Ian Gregson
相关产品推荐
相关产品推荐

