Go 语言实现多个子命令的脚本应用

本页内容

像 Git,Go,Docker 等等的命令行脚本都有很多子命令,那么我们用 Go 语言如何实现多个子命令的脚本应用呢?

例子和源码

先来看一个不含子命令的 CLI 应用:

 1package main
 2
 3import "flag"
 4import "net/http"
 5
 6var handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 7    w.Write([]byte("hello world"))
 8})
 9
10func main() {
11    // 绑定监听地址参数
12    addr := flag.String("addr", ":8080", "addr")
13    // 解析参数
14    flag.Parse()
15
16    http.ListenAndServe(*addr, handler)
17}
1$ go run main.go --addr :23333
2$ curl http://localhost:23333
3hello world

现在我们来查看下 flag 包的源码,可以找到以下代码片段:

1var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
2
3func Parse() {
4    CommandLine.Parse(os.Args[1:])
5}

可以清晰地看到 flag 包中的 CommandLine 是一个顶层的 Flag Set,参数解析是从第二个参数开始。

分析

先从命令格式开始,多个子命令的格式一般为:

script <command> [arguments]
  • 第二个参数是子命令名称
  • 第三个参数开始都是该子命令的参数,可选

在先前的例子,我们其实只需要为每个子命令创建一个新的 Flag Set,然后绑定参数并从第三个参数开始解析即可。

实现

将先前例子的 web 服务改成 serve 子命令,再新增一个 migrate 子命令用于数据迁移:

 1package main
 2
 3import "flag"
 4import "fmt"
 5import "net/http"
 6import "os"
 7
 8var handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 9    w.Write([]byte("hello world"))
10})
11
12func main() {
13    // serve command
14    serveCmd := flag.NewFlagSet("serve", flag.ExitOnError)
15    serveAddr := serveCmd.String("addr", ":8080", "addr")
16
17    // migrate command
18    migrateCmd := flag.NewFlagSet("migrate", flag.ExitOnError)
19    migrateConfig := migrateCmd.String("config", "", "config")
20
21    // 第二个参数为子命令名称
22    var cmd string
23    if len(os.Args) > 1 {
24        cmd = os.Args[1]
25    }
26
27    switch cmd {
28        case "serve":
29            // 从第三个参数开始解析
30            serveCmd.Parse(os.Args[2:])
31            http.ListenAndServe(*serveAddr, handler)
32        case "migrate":
33            migrateCmd.Parse(os.Args[2:])
34            fmt.Println("migrate config:" + *migrateConfig)
35        default:
36            fmt.Println("Usage: \n\tscript <command> [arguments]")
37            os.Exit(1)
38    }
39}
 1$ go run main.go
 2Usage: 
 3        script <command> [arguments]
 4exit status 1
 5
 6$ go run main.go serve --addr :23333
 7$ curl http://localhost:23333
 8hello world
 9
10$ go run main.go migrate --config config.yaml
11migrate config:config.yaml