“灾难性的结果:篡改系统命令后,我惊魂未定地失去了年终奖 – Golang-cobra”

alt

打工还是要打工的。。我最后也没发出去。

紧急处理以后,现在写复盘,大家随我看看我到底是在学习哪些内容。

(以上内容纯属虚构,如有雷同纯属巧合)

简介

之前我们讲过pflag和os.Args,现在说说cobra这个命令行框架。

KubernetesHugoetcd 这些知名项目都用cobra来做命令行程序。学起来!

关于作者spf13,这里多说两句。spf13 开源不少项目,而且他的开源项目质量都比较高。相信使用过 vim 的都知道spf13-vim,号称 vim 终极配置。可以一键配置,对于我这样的懒人来说绝对是福音。

alt

还有他的viper是一个完整的配置解决方案。完美支持 JSON/TOML/YAML/HCL/envfile/Java properties 配置文件等格式,还有一些比较实用的特性,如配置热更新、多查找目录、配置保存等。还有非常火的静态网站生成器hugo也是他的作品牛人就是牛人。

这个牛人 https://github.com/spf13

快速使用

第三方库都需要先安装,后使用。下面命令安装了cobra生成器程序和 cobra 库:

$ go get github.com/spf13/cobra/cobra

PS: 如果出现了golang.org/x/text库找不到之类的错误,需要手动从 GitHub 上下载该库,再执行上面的安装命令。

现在要举的例子是让我们的程序调子命令时会透传到git上,用git version举例。目录结构如下(手动建的):

get-started/
    cmd/       
      root.go        
      version.go    
    utils/
      helper.go
    main.go
  • cmd目标是子命令列表,这里有一个
    version命令。
  • root.go先卖个关子,大家不要理他。
  • main.go是主程序。
  • helper是这里使用到的工具类。
  • go.mod文件我省略了。
  • 下面的代码文件我就省略import "github.com/spf13/cobra"了,大家知道就行,version.go文件:

    var versionCmd = &cobra.Command{
     Use:   "version",
     Short: "version subcommand show git version info.",

     Run: func(cmd *cobra.Command, args []string) {
      output, err := utils.ExecuteCommand("git", "version", args...)
      if err != nil {
       utils.Error(cmd, args, err)
      }

      fmt.Fprint(os.Stdout, output)
     },
    }

    func init() {
     rootCmd.AddCommand(versionCmd)
    }
  • 几个参数含义是子命令名称、子命令短提示、子命令调用的方法
  • init()里把子命令加到主命令中去。
  • 你会有疑惑rootCmd是哪来的吗?实际上我们需要一个根节点,把其他命令加进来。如下是root.go文件。

    var rootCmd = &cobra.Command {
     Use: "git",
     Short: "Git is a distributed version control system.",
     Long: `Git is a free ...省略`,
     Run: func(cmd *cobra.Command, args []string) {
      utils.Error(cmd, args, errors.New("unrecognized command"))
     },
    }

    func Execute() {
     rootCmd.Execute()
    }

    有没有发现这里不是init()而是Execute()?这里此包唯一暴露的公开函数内容,专门供命令初始化使用。如下main.go文件中的调用命令入口:

    import "cmd"
    func main() {
      cmd.Execute()
    }

    最后为了编码方便,在helpers.go中封装了调用外部程序和错误处理函数,我就不展开写了,有兴趣去看我的源码。

    https://github.com/golang-minibear2333/cmd_utils

    cobra 自动生成的帮助信息,very cool

    $ go run . -h
    Git is a free and open source distributed version control system
    designed to handle everything from small to very large projects 
    with speed and efficiency.

    Usage:
      git [flags]
      git [command]

    Available Commands:
      completion  Generate the autocompletion script for the specified shell
      help        Help about any command
      version     version subcommand show git version info.

    Flags:
      -h, --help   help for git

    Use "git [command] --help" for more information about a command.

    单个子命令的帮助信息:

    $ go run . version -h
    version subcommand show git version info.

    Usage:
      git version [flags]

    Flags:
      -h, --help   help for version

    调用子命令:

    $ go run . version
    git version 2.33.0

    未识别的子命令:

    $ go run . xxx
    Error: unknown command "xxx" for "git"
    Run 'git --help' for usage.

    使用 cobra 构建命令行时,程序的目录结构一般比较简单,推荐使用下面这种结构:

    appName/
        cmd/
            cmd1.go
            cmd2.go
            cmd3.go
            root.go
        main.go

    每个命令实现一个文件,所有命令文件存放在cmd目录下。外层的main.go仅初始化 cobra。

    特性

    cobra 提供非常丰富的功能:

  • 轻松支持子命令,如
    app server
    app fetch等;
  • 完全兼容 POSIX 选项(包括短、长选项);
  • 嵌套子命令;
  • 全局、本地层级选项。可以在多处设置选项,按照一定的顺序取用;
  • 使用脚手架轻松生成程序框架和命令。
  • 首先需要明确 3 个基本概念:

  • 命令(Command):就是需要执行的操作;
  • 参数(Arg):命令的参数,即要操作的对象;
  • 选项(Flag):命令选项可以调整命令的行为。
  • 比如

    git clone URL --bare

    clone 是一个(子)命令,URL 是参数,--bare是选项。子命令我们已经讲过了,现在讲讲参数。

    参数

    比如定义命令的地方。

    var cloneCmd = &cobra.Command{
     Use:   "clone url [destination]",
      ...
      Run: func(cmd *cobra.Command, args []string) {
      ...

    会改变帮助函数输出的内容。实际上还是传入字符串数组。

    go run . clone -h
    Clone a repository into a new directory

    Usage:
      git clone url [destination] [flags]

    Flags:
      -h, --help   help for clone

    选项

    cobra 中选项分为两种.

  • 一种是永久选项(
    PersistentFlags 翻译不太标准,暂时就说永久选项),定义它的命令和其子命令都可以使用。方法是给根命令添加一个选项定义全局选项。
  • 另一种是本地选项,只能在定义它的命令中使用。
  • cobra 使用pflag解析命令行选项,上次讲过,实际上用法都是一样的。

    设置永久选项,在root.go根命令文件中的init()函数:

    var(
      Verbose bool
    )
    func init() {
      rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
    }

    设置本地选项,在子命令的init()函数:

    var(
      Source bool
    )
    func init() {
      localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
      rootCmd.AddCommand(divideCmd)
    }

    两种参数都是相同的,长选项/短选项名、默认值和帮助信息。

    脚手架

    通过前面的介绍,我们也看到了其实 cobra 命令的框架还是比较固定的。这就有了工具的用武之地了,可极大地提高我们的开发效率。

    在你的go root下安装cobra-cli,确保bin目录已经放到系统的path里,之前写的文章-运行那一节有提到过怎么操作,不记得的回去看看哈。

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

    下面我们介绍如何使用这个生成器,先看命令帮助:

    Usage:
      cobra-cli init [path] [flags]

    Aliases:
      init, initialize, initialise, create

    Flags:
      -h, --help   help for init

    Global Flags:
      -a, --author string    author name for copyright attribution (default "YOUR NAME")
          --config string    config file (default is $HOME/.cobra.yaml)
      -l, --license string   name of license for the project
          --viper            use Viper for configuration
  • 根据提示子命令
    init,可选参数为
    path
  • 选项为
    -a指定作者,
    --config string指定
    cobra-cli自己的配置文件
  • -l指定
    license
    --viper使用
    viper来读取配置文件。
  • 使用cobra init命令创建一个 cobra 应用程序:

    $ mkdir appname
    $ cd appname
    $ cobra-cli init
    Error: Please run `go mod init <MODNAME>` before `cobra-cli init`
    $ go mod init
    go: creating new go.mod: module github.com/golang-minibear2333/cmd_utils/git/appname
    $ cobra-cli init
    Your Cobra application is ready at
    /Users/xxxx/Documents/code/go/src/github.com/golang-minibear2333/cmd_utils/git/appname
  • 先初始化
    mod 再初始化项目
  • 其中
    appname为应用程序名。生成的程序目录结构如下:
  • .
    ├── LICENSE
    ├── cmd
    │   └── root.go
    ├── go.mod
    ├── go.sum
    └── main.go

    这个项目结构与之前介绍的完全相同,也是 cobra 推荐使用的结构。同样地,main.go也仅仅是入口。里面的英文注释非常的清晰,我一下子就看懂了用法,你也试试。

    配置读取

    除了命令行以外,这个库还可以用来配置读取,我们先创建项目和配置文件:

    mkdir cfg_load && cd_cg_load
    mkdir config && touch config/cfg.yaml
    cat >config/cfg.yaml <<-EOF
    people:
       name: minibear2333
       age: 18
    EOF

    PS: linux命令不熟的可以在Go群里问我。

    现在我们尝试读取这个配置文件,直接使用命令来创建读取配置文件的代码。

    $ cobra-cli init --viper
    Your Cobra application is ready at
    /Users/xxx/Documents/code/go/src/github.com/golang-minibear2333/cmd_utils/git/cfg_load

    现在就创建了一个默认配置文件为$HOME/.cfg_load.yaml的命令行程序,而我们之前放在了另一个位置,所以启动的时候需要指定一下。

    $ go run . --config==config/cfg.yaml

    配置文件就成功载入了,现在你就可以用viper在需要的地方读取配置了。

    为了展示一下配置是否成功读取,继续用cobra-cli来创建一个子命令。

    $ cobra-cli add viperall

    修改此子命令Run函数的内容为

    var viperallCmd = &cobra.Command{
     Use:   "viperall",
     Short: "Show cfg all",
     Long: `Show the contents of the entire configuration file`,
     Run: func(cmd *cobra.Command, args []string) {
      fmt.Println(viper.AllSettings())
     },
    }

    运行,妥了。

    $ go run . viperall --config=config/cfg.yaml 
    Using config file: config/cfg.yaml
    map[people:map[age:18 name:minibear2333]]

    每次都要指定肯定很麻烦,你熟悉viper的话可以自己改一下默认文件,把我的项目下下来给我提交一个pr吧~!

    子命令也可以嵌套,只需要在init()的时候,加到父命令里,当然也可以自动生成。

    $ cobra-cli add tt -p viperallCmd
    tt created at /Users/xxx/Documents/code/go/src/github.com/golang-minibear2333/cmd_utils/git/cfg_load
    $ go run . viperall tt --config=config/cfg.yaml 
    Using config file: config/cfg.yaml
    tt called
  • 注意父命令是
    viperall,但是
    -p指定的时候要改为
    viperallCmd,因为如下(我觉得这个是个很好的贡献pr,你可以建议作者改一下):
  • var viperallCmd = &cobra.Command{

    小结

  • 每个 cobra 程序都有一个根命令,可以给它添加任意多个子命令。比如我们在
    version.go
    init函数中将子命令添加到根命令中。
  • 创建子命令时指定子命令名称、子命令短提示、子命令调用的方法。
  • 三个重要概念,子命令、参数、选项。
  • 全局选项和子命令自己使用的选项。
  • cobra-cli 自动创建项目,自动创建配置文件读取项目,自动增加子命令,自动增加嵌套子命令。
  • 推荐目录结构

    .
    ├── LICENSE
    ├── cmd
    │   ├── root.go
    |   ├── cmd1.go
    ├── go.mod
    ├── go.sum
    └── main.go

    还有更多! cobra 提供了非常丰富的特性和定制化接口,例如:

  • 设置钩子函数,在命令执行前、后执行某些操作。
  • 生成 Markdown/ReStructed Text/Man Page 格式的文档。
  • 等。自己下来学咯。
  • cobra 库的使用非常广泛,很多知名项目都有用到,前面也提到过这些项目。学习这些项目是如何使用 cobra 的,可以从中学习 cobra 的特性和最佳实践。这也是学习开源项目的一个很好的途径。

    本文由 mdnice 多平台发布

    物联沃分享整理
    物联沃-IOTWORD物联网 » “灾难性的结果:篡改系统命令后,我惊魂未定地失去了年终奖 – Golang-cobra”

    发表评论