
go 1.16+ 支持 `embed` 包,可将 bash 脚本以字符串形式编译进二进制;配合 `exec.command(“bash”)` 并设置 `stdin`,即可直接执行,无需外部文件依赖,完美支持交叉编译。
在 go 应用中动态执行 Shell 脚本是常见需求(如初始化环境、调试辅助、CI 工具封装等),但传统方式需分发脚本文件,破坏单一二进制优势,且易因路径或权限问题失败。Go 1.16 引入的 embed 包为此提供了优雅解法:将脚本内容静态嵌入编译后的二进制中,并通过标准输入(stdin)交由 bash 或 sh 解释执行。
✅ 基础实现:嵌入 + 执行
只需两步:
- 使用 //go:embed 指令声明脚本文件(如 script.sh);
- 创建 exec.Command(“bash”),并将嵌入的脚本内容作为 Strings.NewReader(script) 赋给 c.Stdin。
package main import ( "embed" "fmt" "os/exec" "strings" ) //go:embed script.sh var script string // 类型为 string,自动读取文件 UTF-8 内容 func main() { cmd := exec.Command("bash") cmd.Stdin = strings.NewReader(script) output, err := cmd.Output() if err != nil { fmt.printf("执行失败: %vn", err) return } fmt.Println(string(output)) }
⚠️ 注意事项:
- embed 仅支持 string 和 []byte 类型变量;若需二进制脚本(如含非 UTF-8 字符),请改用 []byte + bytes.NewReader();
- 确保目标系统已安装 bash(或改用 sh 提高兼容性);
- 脚本中使用 $0, $1, $@ 等参数时,需显式传参(见下文“带参执行”);
- script.sh 必须位于当前包目录或子目录中,且不能是隐藏文件(以 . 开头)或 testdata/ 目录下的文件。
? 进阶技巧:向嵌入脚本传递参数
Bash 支持 -s 标志从 stdin 读取脚本,并将后续参数作为 , , … $@ 传入:
package main import ( "fmt" "os/exec" "strings" ) func main() { // -s: 从 stdin 读脚本;-: 占位符(表示脚本结束位置);后续为 $1, $2... cmd := exec.Command("bash", "-s", "-", "hello", "world") cmd.Stdin = strings.NewReader(` echo "参数个数: $#" echo "第一个参数: $1" echo "第二个参数: $2" echo "全部参数: $@" `) out, err := cmd.Output() if err != nil { fmt.Printf("执行出错: %vn", err) return } fmt.Print(string(out)) }
输出示例:
参数个数: 2 第一个参数: hello 第二个参数: world 全部参数: hello world
? 验证与最佳实践
- 交叉编译安全:embed 在构建时完成内容注入,不依赖运行时文件系统,GOOS=linux GOARCH=arm64 go build 可生成纯静态 ARM64 二进制;
- 调试建议:开发期可用 fmt.Printf(“嵌入脚本:n%sn”, script) 输出验证内容是否正确加载;
- 安全提醒:避免拼接用户输入到嵌入脚本中(防止命令注入),如需动态逻辑,请改用 Go 原生实现或严格参数化调用;
- 替代方案对比:相比 text/template 渲染或 os.ReadFile,embed 零 I/O、零依赖、启动即用,是嵌入式/CLI 工具的首选。
通过 embed + exec 组合,你既能享受 Go 单体二进制的部署便利,又能复用成熟的 Shell 生态——无需妥协,开箱即用。