1

Go通过cobra快速构建命令行应用 - iqsing

 2 years ago
source link: https://www.cnblogs.com/qsing/p/16166265.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

来自jetbrains Go 语言现状调查报告 显示:在go开发者中使用go开发实用小程序的比例为31%仅次于web,go得益于跨平台、无依赖的特性,用来编写命令行或系统管理这类小程序非常不错。

image-20220418152624008

本文主要介绍Steve Francia(spf13)大神写的用于快速构建命令行程序的golang包cobra,基于cobra写命令行的著名项目一只手数不过来:Docker CLI、Helm、istio、etcd、Git、Github CLI ...

下面进入正题

cobra能帮我们做啥?


cobra包提供以下功能:

  • 轻松创建基于子命令的 CLI:如app serverapp fetch等。
  • 自动添加-h,--help等帮助性Flag
  • 自动生成命令和Flag的帮助信息
  • 创建完全符合 POSIX 的Flag(标志)(包括长、短版本)
  • 支持嵌套子命令
  • 支持全局、本地和级联Flag
  • 智能建议(app srver... did you mean app server?)
  • 为应用程序自动生成 shell 自动完成功能(bash、zsh、fish、powershell)
  • 为应用程序自动生成man page
  • 命令别名,可以在不破坏原有名称的情况下进行更改
  • 支持灵活自定义help、usege等。
  • 无缝集成viper构建12-factor应用

cobra遵循commands, arguments & flags结构。

#appname command  arguments
docker pull alpine:latest
#appname command flag
docker ps -a
#appname command flag argument
git commit -m "msg"

开发者可根据情况进行自组织。

cobra基础使用


安装cobra包和二进制工具cobra-cli,cobra-cli可以帮助我们快速创建出一个cobra基础代码结构。

go get -u github.com/spf13/cobra@latest
go install github.com/spf13/cobra-cli@latest

启用GO111MODULE=on,我们初始化一个xpower

# go mod init  xpower
go: creating new go.mod: module xpower

使用cobra-cli初始化基础代码结构

# cobra-cli  init
Your Cobra application is ready at /root/demo/xpower

#查看目录结构
# tree xpower
xpower
├── cmd
│   └── root.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go

1 directory, 5 files

运行demo可以看到cobra包本身的一些提示信息。

image-20220419103917739

查看main.go,cobra-cli为我们创建了一个cmd的包并且调用了包里面的Execute()函数

/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>

*/
package main

import "xpower/cmd"

func main() {
        cmd.Execute()
}

从上面的目录结构中可以看到cmd包目前只有一个root.go,我们可以在这里操作根命令相关的内容。

大多数时候CLI可能会包含多个子命令比如git clonegit add,cobra-cli可通过add 添加子命令。

现在我们添加wget和ping子命令,即接下来我们将通过xpower来重写wget和ping的部分功能。

cobra-cli add wget
cobra-cli add ping 

现在的目录结构如下:

# tree xpower
xpower
├── cmd
│   ├── ping.go
│   ├── root.go
│   └── wget.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go

pingwget已经被集成到root.go中

image-20220419112101887

wget.go

package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

// wgetCmd represents the wget command
var wgetCmd = &cobra.Command{
    Use:     "wget",
    Example: "xpower wget iqsing.github.io/download.tar -o /tmp",
    Short:   "wget is a download cli.",
    Long:    `use wget to download everything you want from net.`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("wget called")  
    },
}

func init() {
    rootCmd.AddCommand(wgetCmd)

    // Here you will define your flags and configuration settings.
}

在wget.go 中定义了一个wgetCmd结构体指针,可通过查看Command结构体原型添加或移除成员变量。这里我们添加了一个Example用于指示示例,Short和Long为命令简介,Run为wget命令的真正实现。

我们知道在go中包的init()函数会在import时执行,通过AddCommand(wgetCmd)将wegetCmd添加到结构体Command 成员变量commands中,包括后面我们编写的Flag也是如此。

接下来我们在结构体中添加Args用于验证(限制)参数数量,在init()函数中添加Flag -o用于保存下载的文件地址,并通过MarkFlagRequired约束flag的参数必须输入,最后在Run中调用Download即可。

package cmd

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "os"

    "github.com/spf13/cobra"
)

var (
    output string
)

// wgetCmd represents the wget command
var wgetCmd = &cobra.Command{
    Use:     "wget",
    Example: "xpower wget iqsing.github.io/download.tar.gz -o /tmp/download.tar.gz",
    Args:    cobra.ExactArgs(1),
    Short:   "wget is a download cli.",
    Long:    `use wget to download everything you want from net.`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("---wget running---")
        Download(args[0], output)
    },
}

func init() {
    rootCmd.AddCommand(wgetCmd)
    // Here you will define your flags and configuration settings.

    wgetCmd.Flags().StringVarP(&output, "output", "o", "", "output file")
    wgetCmd.MarkFlagRequired("output")
}
func Download(url string, path string) {
    out, err := os.Create(path)
    check(err)
    defer out.Close()

    res, err := http.Get(url)
    check(err)
    defer res.Body.Close()

    _, err = io.Copy(out, res.Body)
    check(err)
    fmt.Println("save as" + path)
}
func check(err error) {
    if err != nil {
        log.Fatal(err)
    }
}
Args:    cobra.ExactArgs(1)

cobra内置的参数验证也是比较多,NoArgs、OnlyValidArgs、MinimumNArgs、MaximumNArgs等等可翻阅源码args.go,可以满足基本使用,如果有自己的特殊要求可以通过解析arg来实现。

flags
wgetCmd.Flags().StringVarP(&output, "output", "o", "", "output file(required)")

flag包含局部和全局两种,全局flag在父命令定义后子命令也会生效,而局部flag则在哪定义就在哪生效。

如上面的局部flag,我们在wgetCmd中定义的flag只有wget这个子命令能用。

全局flag

rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

StringVarpBoolVarP 用于flag数据类型限制。

简单的应用从命令行直接写入参数是很常见的,但是如果比较复杂的命令行应用参数需要非常多,再这样操作不太合理,cobra作者还写了另一个在go中很流行的包viper用于解析配置文件,比如kubectl 的yml,以及各种json

前面也说过可以无缝衔接,只需Bind一下即可。

var author string

func init() {
  rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
  viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}

flag还可以做依赖,比如下面username和password必须同时接收到参数。

rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)")
rootCmd.Flags().StringVarP(&pw, "password", "p", "", "Password (required if username is set)")
rootCmd.MarkFlagsRequiredTogether("username", "password")

添加子命令可参考包go-ping/ping,这里不再赘述。

我们来看编译后使用如何?

通过-h查看帮助:

image-20220419153813818

参数个数错误:

image-20220419154100059

需要flag-o

image-20220419154336330

正确使用:

xpower 子命令ping:

image-20220419154528095

xpower 子命令wget:

image-20220419154738554


以上我们通过go中cobra包实现xpower命令,包含重写了简单功能的ping和wget两子命令,甚至我们还可以以此来实现自己的跨平台、无依赖的工具集。本文涉及代码已提交至仓库code/xpower

cobra包含很多开箱即用的功能,经过大量项目验证和完善,已满足大部分命令行应用构建需求。本文只介绍了一部分内容,更多内容可查看仓库spf13/cobra

通过博客阅读:iqsing.github.io


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK