
本文介绍在 Go 中使用 exec.Command 执行外部命令时,如何安全、可靠地同时捕获 stdout/stderr 并可选地实时输出到控制台,重点解决因 goroutine 异步执行导致的竞态与输出不一致问题。
本文介绍在 go 中使用 `exec.command` 执行外部命令时,如何安全、可靠地同时捕获 stdout/stderr 并可选地实时输出到控制台,重点解决因 goroutine 异步执行导致的竞态与输出不一致问题。
在 Go 中调用外部命令(如 ls、curl 或 dir)并捕获其输出是常见需求,但若未正确同步 I/O 管道读取与命令生命周期,极易引发数据丢失、截断或两次调用结果不一致等问题——正如原始代码所示:command.Run() 返回后,后台 goroutine 可能仍在从 stdout/stderr 读取数据,导致 output.String() 提前返回不完整内容。
根本原因在于:io.Copy 是阻塞操作,而原代码中启动 goroutine 后未等待其完成就直接返回,造成竞态条件(race condition)。修复的核心思路是:确保命令进程退出后,所有管道读取 goroutine 也已全部完成。
以下是一个健壮、可复用的实现方案:
func run(cmd *exec.Cmd, displayOutput bool) (string, error) {
var output bytes.Buffer
var wg sync.WaitGroup
// 处理 stdout
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", fmt.Errorf("failed to get stdout pipe: %w", err)
}
wg.Add(1)
go func() {
defer wg.Done()
if displayOutput {
io.Copy(io.MultiWriter(os.Stdout, &output), stdout)
} else {
io.Copy(&output, stdout)
}
}()
// 处理 stderr(同理)
stderr, err := cmd.StderrPipe()
if err != nil {
return "", fmt.Errorf("failed to get stderr pipe: %w", err)
}
wg.Add(1)
go func() {
defer wg.Done()
if displayOutput {
io.Copy(io.MultiWriter(os.Stderr, &output), stderr)
} else {
io.Copy(&output, stderr)
}
}()
// 启动并等待命令完成
if err := cmd.Start(); err != nil {
return "", fmt.Errorf("failed to start command: %w", err)
}
// 注意:必须先 wait,再 wg.Wait —— 因为管道读取依赖于进程结束触发 EOF
if err := cmd.Wait(); err != nil {
// 即使命令失败,也要等 goroutines 完成,避免资源泄漏
wg.Wait()
return "", fmt.Errorf("command failed: %w", err)
}
// 等待所有 io.Copy 完成
wg.Wait()
return output.String(), nil
}✅ 关键要点说明:
- 使用 sync.WaitGroup 显式跟踪所有异步 io.Copy goroutine 的完成状态;
- 调用 cmd.Wait()(而非 cmd.Run())更可控,便于错误处理与资源清理;
- wg.Wait() 必须在 cmd.Wait() 之后调用,因为 io.Copy 依赖管道 EOF(由子进程退出触发),过早等待将永久阻塞;
- 错误路径中仍需 wg.Wait(),防止 goroutine 泄漏;
- 若需区分 stdout/stderr 内容,可分别使用独立 bytes.Buffer 并合并逻辑,本例统一捕获以简化场景。
? 进阶建议:
- 对超长输出,考虑添加上下文超时(cmd.WithContext(ctx))防止挂起;
- 生产环境应限制命令执行时间、内存占用,并校验命令参数防注入;
- 如需结构化错误(如退出码、信号终止),可通过 cmd.ProcessState.ExitCode() 或 Signal() 获取。
通过以上设计,run() 函数能保证每次调用输出完全一致、无竞态、可预测,真正满足日志记录、自动化脚本或 CLI 工具等生产级需求。

















