BY Y!an - 2025年9月27日

Go 实用技巧:执行外部程序,像 tail -f 一样获取流式输出

水一篇 Go 小技巧

Go 在使用 os/exec 执行外部命令的时候,假如外部命令是持续输出的,该如何实时、持续获取外部命令的输出呢?

其实很简单,只需要通过 StdoutPipe() 创建一个管道接收外部命令的标准输出即可。

先来准备一个外部命令程序 ./hello/main.cc

#include "iostream"
#include <thread>

int main() {
    while (true) {
        auto now = std::chrono::system_clock::now();
        std::time_t now_c = std::chrono::system_clock::to_time_t(now);
        std::cout << "Current time is: " << std::ctime(&now_c) << std::flush;
        // sleep for a second
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

然后用 g++ 编译:g++ -o hello main.cc 得到 ./hello/hello,这个程序会每隔一秒输出当前时间。

接下来开始写我们的 Go 程序:

package main

import (
	"fmt"
	"os/exec"
	"strings"
)

func main() {
	cmd := exec.Command("./hello/hello")

	cmdStdout, err := cmd.StdoutPipe() // 创建一个管道接收外部命令的标准输出
	if err != nil {
		fmt.Println("Error creating StdoutPipe:", err)
		return
	}

	if err := cmd.Start(); err != nil {
		fmt.Println("Error starting command:", err)
		return
	}

	// 用协程非阻塞接收外部命令的流式输出
	go func() {
		for {
			buf := make([]byte, 1024)
			n, err := cmdStdout.Read(buf)
			if err != nil {
				fmt.Println("Error reading from stdout:", err)
				break
			}

			if n == 0 {
				continue
			}

			// 按需把尾部的换行符、或者是 '\0' (在 Go 里是 "\x00")去除掉
			output := strings.TrimSuffix(string(buf[:n]), "\n")
			fmt.Println("Output:", output)
		}
	}()

	if err := cmd.Wait(); err != nil {
		fmt.Println("Error waiting for command:", err)
		return
	}
}

如果你觉得文章对你有些帮助,可以请我喝杯咖啡 ↓