go mod 仅负责依赖管理,不具备任务编排能力,无法替代 make、bash 等脚本工具;真正可替代脚本的是用 Go 编写带 main 函数的 CLI 工具,通过 go run 或 go install 执行,利用标准库调用外部命令、处理路径和参数。

为什么 go mod 本身不能直接替代脚本工具
很多人以为 go mod 装完依赖就完事了,结果发现还得写一堆 make、bash 或 just 脚本来拉依赖、跑测试、打镜像、发版本——go mod 只管依赖管理,不提供任务编排能力。它不是构建系统,也不是 CI 封装器。
真正能“替代脚本”的,是把 Go 当成通用命令行工具语言来用:用 main 函数封装逻辑,用标准库(如 os/exec、filepath、flag)调外部命令、解析路径、接收参数,再通过 go install 或 go run 直接执行。
用 go run + 内置命令快速替代 shell 脚本
比如你原来有个 build.sh 做三件事:检查 Go 版本、生成 protobuf、打包二进制。现在可以写一个 build.go,用 go run build.go 一键跑完,不用提前 chmod +x 或维护 PATH。
-
runtime.Version()检查 Go 版本,比go version | grep更可靠 - 用
exec.Command("protoc", ...)调用 protoc,错误可统一捕获,不用靠set -e - 用
filepath.Walk扫描 proto 文件,比find . -name "*.proto"更可控 - 所有路径拼接用
filepath.Join,避免 Unix/Windows 斜杠不一致问题
示例片段:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func main() {
if !strings.HasPrefix(runtime.Version(), "go1.21") {
log.Fatal("requires go1.21+")
}
cmd := exec.Command("protoc", "--go_out=.", "api/*.proto")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
用 go install 构建可复用的 CLI 工具链
当某个逻辑反复出现(比如生成 client SDK、校验 config.yaml 格式、批量重命名文件),不要每次 go run,而是用 go install 编译成全局命令。这样它就跟 curl、jq 一样可用,且天然跨平台。
- 入口必须是
package main,且main()函数存在 - 模块路径要带域名前缀(如
example.com/tools/gen),否则go install会报no module provides package - 推荐在
cmd/下放多个子命令(cmd/gen、cmd/verify),比单个大 binary 更易维护 - 注意
GOBIN环境变量,默认是$HOME/go/bin,确保它在$PATH中
安装后直接运行:gen --input api/ --output client/,无需 ./gen 或指定路径。
哪些场景不适合硬切 Go 替代脚本
不是所有脚本都值得重写。Go 启动慢、语法啰嗦、对纯文本流处理(如 sed/awk 链式过滤)不如 shell 直观。以下情况建议保留原脚本或混合使用:
- 单行管道操作,比如
ps aux | grep nginx | awk '{print $2}' | xargs kill—— Go 写出来反而难读 - 需要快速原型验证的临时逻辑,
go run编译耗时 > 你改三次 bash 的时间 - 依赖大量非标准命令(如特定发行版才有的
dnf repoquery),Go 调用时得做兼容判断,成本高于 shell 的which+ fallback - CI 中已稳定运行多年的 Makefile,除非它频繁出错或难以调试,否则别为“统一语言”而重构
真正该替换的,是那些逻辑复杂、需要结构化错误处理、要跨团队共享、或经常被误删/改错的脚本——它们早就不只是“胶水”,而是隐性业务组件了。

















