
本文介绍如何在 Go 程序中模拟 yes | ./install.sh 的行为,即自动向待安装脚本持续输入 "y"(或默认确认响应),解决 exec.Command 无法直接解析 shell 管道符的问题。
本文介绍如何在 go 程序中模拟 yes | ./install.sh 的行为,即自动向待安装脚本持续输入 "y"(或默认确认响应),解决 exec.command 无法直接解析 shell 管道符的问题。
在 Go 中使用 os/exec 包执行外部命令时,exec.Command 默认不经过 shell 解析,因此像 yes | ./install.sh 这样的管道语法不能直接作为参数传入(如 exec.Command("bash", "-c", "yes | ./install.sh") 虽可行,但需谨慎处理 I/O 和错误传播)。更健壮、可控的方式是显式构建进程管道:启动 yes 作为生产者,将其标准输出连接到目标脚本的标准输入。
以下为推荐实现方案(已验证可用):
package main
import (
"os"
"os/exec"
)
func main() {
// 启动 yes 命令(默认持续输出 "y\n")
cmd1 := exec.Command("/bin/bash", "-c", "yes")
// 启动安装脚本(注意:路径需可执行,且建议使用绝对路径或确保 PATH 正确)
cmd2 := exec.Command("./install.sh") // 替换为实际脚本路径
// 将 cmd1 的 stdout 管道连接到 cmd2 的 stdin
var err error
cmd2.Stdin, err = cmd1.StdoutPipe()
if err != nil {
panic("failed to create stdin pipe: " + err.Error())
}
// 可选:将脚本输出透传到当前终端(便于调试)
cmd2.Stdout = os.Stdout
cmd2.Stderr = os.Stderr
// 必须先 Start cmd2,再 Run cmd1(否则可能死锁)
if err = cmd2.Start(); err != nil {
panic("failed to start install.sh: " + err.Error())
}
// 启动 yes 并等待其完成(它会持续输出直到 cmd2 退出并关闭 stdin)
if err = cmd1.Run(); err != nil {
panic("failed to run 'yes': " + err.Error())
}
// 等待 install.sh 完全退出
if err = cmd2.Wait(); err != nil {
panic("install.sh failed: " + err.Error())
}
}
✅ 关键要点说明:
- 使用
exec.Command("/bin/bash", "-c", "yes")启动yes,比直接调用yes更具兼容性(避免某些系统无yes命令或路径问题); -
cmd2.Stdin, _ = cmd1.StdoutPipe()建立管道连接,必须在cmd2.Start()之前调用; -
cmd2.Start()和cmd1.Run()的调用顺序很重要:先启动子进程,再驱动输入源,防止管道阻塞; - 若脚本依赖交互式 TTY(如检测
isatty),此方法可能失效——此时应考虑改用script命令或expect类工具; - 生产环境建议添加超时控制(如
cmd2.Wait()前设置time.AfterFunc)和更完善的错误处理(而非panic)。
该方案规避了 shell 字符串拼接的安全风险(如命令注入),符合 Go 的显式 I/O 管理哲学,是自动化部署场景下的可靠实践。










