本文详解如何正确使用 exec.Command 配合 StdoutPipe 和 bufio.Scanner 逐行处理子进程输出,重点解决因 shell 特性(如 {1..5} 展开)导致的输出截断问题,并提供安全、高效的实践方案。
本文详解如何正确使用 `exec.command` 配合 `stdoutpipe` 和 `bufio.scanner` 逐行处理子进程输出,重点解决因 shell 特性(如 `{1..5}` 展开)导致的输出截断问题,并提供安全、高效的实践方案。
在 Go 中调用外部命令并实时、逐行解析其标准输出是常见需求(例如日志流处理、CLI 工具集成等)。但初学者常误以为 exec.Command 会自动执行 shell 解析(如 bash 的 brace expansion {1..5}),从而导致命令行为与预期不符——正如示例中仅输出一行而非五行。
根本原因在于:exec.Command 默认不经过 shell,它直接调用 execve() 系统调用执行二进制程序,因此 printf "{\"Name\": \"Bob\", \"Age\": 1}\n%.0s" {1..5} 中的 {1..5} 不会被展开,printf 实际只接收两个参数,%.0s 格式符也因缺少对应参数而失效,最终仅打印一次。
✅ 正确做法是:显式构造所有参数,或通过 shell 显式启动。推荐前者以避免 shell 注入风险和平台差异:
package main
import (
"encoding/json"
"fmt"
"log"
"os/exec"
"bufio"
)
func main() {
// ✅ 方案一:手动展开参数(安全、跨平台、推荐)
args := make([]string, 6) // "printf" + 5 次 "%d"
args[0] = "printf"
for i := 1; i <p>⚠️ 注意事项:</p>
- 永远不要用 exec.Command("sh", "-c", "...") 直接拼接用户输入,存在严重命令注入漏洞;
- scanner.Text() 返回 string,需转 []byte 才能用于 json.Unmarshal;而 scanner.Bytes() 直接返回底层字节切片,性能更好且避免拷贝;
- scanner.Scan() 在遇到 I/O 错误或 EOF 时返回 false,务必检查 scanner.Err() 判断是否为真实错误;
- 若需同时捕获 stderr,可调用 cmd.StderrPipe() 并单独 goroutine 处理,避免阻塞;
- 对于超长行(默认 64KB 限制),可通过 scanner.Buffer(nil, 1
总结:Go 的 os/exec 设计强调显式与安全。逐行读取命令输出的关键在于——理解 exec.Command 的无 shell 特性,手动构造参数,善用 bufio.Scanner 的流式能力,并始终校验错误路径。这样即可稳定、高效地集成任意命令行工具。











