如何在 Go 中调用系统命令行 Shell 并实现交互式执行

9次阅读

如何在 Go 中调用系统命令行 Shell 并实现交互式执行

本文介绍如何使用 go 的 `os/exec` 包安全、高效地调用系统 shell(如 bash、zsh),并实现标准输入 / 输出 / 错误的双向管道通信,支持交互式会话与批处理命令执行。

在 Go 中与系统命令行 Shell 交互,核心在于正确管理进程生命周期和 I/O 管道。Go 标准库 的 os/exec 包提供了强大而底层的接口,但需手动处理 stdin/stdout/stderr 的读写同步,否则易出现阻塞、死锁或输出丢失等问题。

以下是一个完整、可运行的示例程序,它能:

  • 启动交互式 Shell(如 bash),支持用户实时输入与命令响应;
  • 执行一次性命令(如 date、ping -c 3 google.com),并原样输出结果;
  • 使用 goroutine + sync.WaitGroup 实现多路 I/O 并发转发,避免管道阻塞。
package main  import ("bufio"     "fmt"     "io"     "log"     "os"     "os/exec"     "sync")  // getPipes 为命令创建标准流管道,失败时直接 panic(演示场景下简化错误处理)func getPipes(c *exec.Cmd) (inp io.Writer, outp, errp io.Reader) {var err error     if inp, err = c.StdinPipe(); err != nil {log.Fatal("stdin pipe:", err)     }     if outp, err = c.StdoutPipe(); err != nil {         log.Fatal("stdout pipe:", err)     }     if errp, err = c.StderrPipe(); err != nil {         log.Fatal("stderr pipe:", err)     }     return }  // pipe 将输入流(如 stdout)逐字节复制到输出流(如 os.Stdout),支持并发转发 func pipe(wg *sync.WaitGroup, inp io.Reader, outp io.Writer) {if wg != nil {         defer wg.Done()     }     r := bufio.NewReader(inp)     for {b, err := r.ReadByte()         if err != nil {break // EOF 或其他错误,退出循环}         _, _ = fmt.Fprint(outp, string(b))     } }  // Command 执行指定命令,自动处理参数拆分与 I/O 管道绑定 func Command(args ……string) {if len(args) == 0 {log.Fatal("no command specified")     }      cmd := exec.Command(args[0], args[1:]……)     stdin, stdout, stderr := getPipes(cmd)      var wg sync.WaitGroup     wg.Add(2)     go pipe(&wg, stdout, os.Stdout)     go pipe(&wg, stderr, os.Stderr)     go pipe(nil, os.Stdin, stdin) // 不等待 stdin 转发完成(用户可能持续输入)if err := cmd.Start(); err != nil {         log.Fatalf("failed to start %v: %v", args, err)     }      // 等待命令退出(对交互式 shell 来说,即用户输入 exit 或 Ctrl+D)if err := cmd.Wait(); err != nil {         log.Printf("command %v exited with error: %v", args, err)     }      wg.Wait() // 确保所有输出已刷新完毕}  func main() {     // 示例:启动交互式 bash 会话(退出后继续执行后续命令)Command("bash")      // 示例:执行一次性命令     Command("date")     Command("echo", "some text")     Command("ping", "-c", "3", "www.google.com") }

关键要点说明

  • exec.Command() 创建命令对象,不立即执行;必须调用 Start() 启动进程,Wait() 等待结束;
  • StdinPipe() / StdoutPipe() 返回的 io.ReadWriter 需由 goroutine 并发处理,否则因缓冲区满导致阻塞(尤其 ping 或长输出命令);
  • 对于交互式 Shell(如 bash),cmd.Wait() 会阻塞直到 shell 进程退出(用户输入 exit 或关闭终端),此时程序才继续执行下一条 Command;
  • 生产环境建议替换 log.Fatal 为可恢复的错误处理,并添加上下文(context.Context)支持超时与取消;
  • 安全性提醒:避免将不可信输入直接拼入 args 数组(防止命令注入),尤其调用 /bin/sh -c 时;优先使用显式参数列表而非 shell 解析。

该方案跨平台兼容(Linux/macOS/Windows),是构建轻量系统 工具、CLI 自动化脚本或嵌入式 Shell 控制台的理想起点。

text=ZqhQzanResources