要命!我篡改了系统命令惊现事故,竟要扣我年终奖-Golang-cobra

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

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

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

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

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

还有他的 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/<br>&#xA0;&#xA0;&#xA0;&#xA0;cmd/&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;root.go&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;version.go&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;utils/<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;helper.go<br>&#xA0;&#xA0;&#xA0;&#xA0;main.go
  • cmd 目标是子命令列表,这里有一个 version 命令。
  • root.go 先卖个关子,大家不要理他。
  • main.go 是主程序。
  • helper 是这里使用到的工具类。
  • go.mod 文件我省略了。

下面的代码文件我就省略 import "github.com/spf13/cobra"了,大家知道就行, version.go文件:

<span class="hljs-keyword">var</span>&#xA0;versionCmd&#xA0;=&#xA0;&cobra.Command{<br>&#xA0;Use:&#xA0;&#xA0;&#xA0;<span class="hljs-string">"version"</span>,<br>&#xA0;Short:&#xA0;<span class="hljs-string">"version&#xA0;subcommand&#xA0;show&#xA0;git&#xA0;version&#xA0;info."</span>,<br><br>&#xA0;Run:&#xA0;<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cmd&#xA0;*cobra.Command,&#xA0;args&#xA0;[]<span class="hljs-keyword">string</span>)</span></span>&#xA0;{<br>&#xA0;&#xA0;output,&#xA0;err&#xA0;:=&#xA0;utils.ExecuteCommand(<span class="hljs-string">"git"</span>,&#xA0;<span class="hljs-string">"version"</span>,&#xA0;args...)<br>&#xA0;&#xA0;<span class="hljs-keyword">if</span>&#xA0;err&#xA0;!=&#xA0;<span class="hljs-literal">nil</span>&#xA0;{<br>&#xA0;&#xA0;&#xA0;utils.Error(cmd,&#xA0;args,&#xA0;err)<br>&#xA0;&#xA0;}<br><br>&#xA0;&#xA0;fmt.Fprint(os.Stdout,&#xA0;output)<br>&#xA0;},<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">func</span>&#xA0;<span class="hljs-title">init</span><span class="hljs-params">()</span></span>&#xA0;{<br>&#xA0;rootCmd.AddCommand(versionCmd)<br>}
  • 几个参数含义是子命令名称、子命令短提示、子命令调用的方法
  • init() 里把子命令加到主命令中去。

你会有疑惑 rootCmd是哪来的吗?实际上我们需要一个根节点,把其他命令加进来。如下是 root.go文件。

<span class="hljs-keyword">var</span>&#xA0;rootCmd&#xA0;=&#xA0;&cobra.Command&#xA0;{<br>&#xA0;Use:&#xA0;<span class="hljs-string">"git"</span>,<br>&#xA0;Short:&#xA0;<span class="hljs-string">"Git&#xA0;is&#xA0;a&#xA0;distributed&#xA0;version&#xA0;control&#xA0;system."</span>,<br>&#xA0;Long:&#xA0;<span class="hljs-string">Git is a free ...省略</span>,<br>&#xA0;Run:&#xA0;<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cmd&#xA0;*cobra.Command,&#xA0;args&#xA0;[]<span class="hljs-keyword">string</span>)</span></span>&#xA0;{<br>&#xA0;&#xA0;utils.Error(cmd,&#xA0;args,&#xA0;errors.New(<span class="hljs-string">"unrecognized&#xA0;command"</span>))<br>&#xA0;},<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">func</span>&#xA0;<span class="hljs-title">Execute</span><span class="hljs-params">()</span></span>&#xA0;{<br>&#xA0;rootCmd.Execute()<br>}

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

import&#xA0;<span class="hljs-string">"cmd"</span><br>func&#xA0;<span class="hljs-function"><span class="hljs-title">main</span></span>()&#xA0;{<br>&#xA0;&#xA0;cmd.Execute()<br>}

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

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

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

$&#xA0;go&#xA0;run&#xA0;.&#xA0;-h<br>Git&#xA0;is&#xA0;a&#xA0;free&#xA0;and&#xA0;open&#xA0;<span class="hljs-built_in">source</span>&#xA0;distributed&#xA0;version&#xA0;control&#xA0;system<br>designed&#xA0;to&#xA0;handle&#xA0;everything&#xA0;from&#xA0;small&#xA0;to&#xA0;very&#xA0;large&#xA0;projects&#xA0;<br>with&#xA0;speed&#xA0;and&#xA0;efficiency.<br><br>Usage:<br>&#xA0;&#xA0;git&#xA0;[flags]<br>&#xA0;&#xA0;git&#xA0;[<span class="hljs-built_in">command</span>]<br><br>Available&#xA0;Commands:<br>&#xA0;&#xA0;completion&#xA0;&#xA0;Generate&#xA0;the&#xA0;autocompletion&#xA0;script&#xA0;<span class="hljs-keyword">for</span>&#xA0;the&#xA0;specified&#xA0;shell<br>&#xA0;&#xA0;<span class="hljs-built_in">help</span>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;Help&#xA0;about&#xA0;any&#xA0;<span class="hljs-built_in">command</span><br>&#xA0;&#xA0;version&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;version&#xA0;subcommand&#xA0;show&#xA0;git&#xA0;version&#xA0;info.<br><br>Flags:<br>&#xA0;&#xA0;-h,&#xA0;--<span class="hljs-built_in">help</span>&#xA0;&#xA0;&#xA0;<span class="hljs-built_in">help</span>&#xA0;<span class="hljs-keyword">for</span>&#xA0;git<br><br>Use&#xA0;<span class="hljs-string">"git&#xA0;[command]&#xA0;--help"</span>&#xA0;<span class="hljs-keyword">for</span>&#xA0;more&#xA0;information&#xA0;about&#xA0;a&#xA0;<span class="hljs-built_in">command</span>.<br>

单个子命令的帮助信息:

$&#xA0;go&#xA0;run&#xA0;.&#xA0;version&#xA0;-h<br>version&#xA0;subcommand&#xA0;show&#xA0;git&#xA0;version&#xA0;info.<br><br>Usage:<br>&#xA0;&#xA0;git&#xA0;version&#xA0;[flags]<br><br>Flags:<br>&#xA0;&#xA0;-h,&#xA0;--<span class="hljs-built_in">help</span>&#xA0;&#xA0;&#xA0;<span class="hljs-built_in">help</span>&#xA0;<span class="hljs-keyword">for</span>&#xA0;version

调用子命令:

$&#xA0;go&#xA0;run&#xA0;.&#xA0;version<br>git&#xA0;version&#xA0;2.33.0

未识别的子命令:

$&#xA0;go&#xA0;run&#xA0;.&#xA0;xxx<br>Error:&#xA0;unknown&#xA0;<span class="hljs-built_in">command</span>&#xA0;<span class="hljs-string">"xxx"</span>&#xA0;<span class="hljs-keyword">for</span>&#xA0;<span class="hljs-string">"git"</span><br>Run&#xA0;<span class="hljs-string">'git&#xA0;--help'</span>&#xA0;<span class="hljs-keyword">for</span>&#xA0;usage.

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

appName/<br>&#xA0;&#xA0;&#xA0;&#xA0;cmd/<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;cmd1.go<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;cmd2.go<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;cmd3.go<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;root.go<br>&#xA0;&#xA0;&#xA0;&#xA0;main.go

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

cobra 提供非常丰富的功能:

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

首先需要明确 3 个基本概念:

  • 命令(Command):就是需要执行的操作;
  • 参数(Arg):命令的参数,即要操作的对象;
  • 选项(Flag):命令选项可以调整命令的行为。
git&#xA0;<span class="hljs-built_in">clone</span>&#xA0;URL&#xA0;--bare

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

比如定义命令的地方。

<span class="hljs-keyword">var</span>&#xA0;cloneCmd&#xA0;=&#xA0;&cobra.Command{<br>&#xA0;Use:&#xA0;&#xA0;&#xA0;<span class="hljs-string">"clone&#xA0;url&#xA0;[destination]"</span>,<br>&#xA0;&#xA0;...<br>&#xA0;&#xA0;Run:&#xA0;<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cmd&#xA0;*cobra.Command,&#xA0;args&#xA0;[]<span class="hljs-keyword">string</span>)</span></span>&#xA0;{<br>&#xA0;&#xA0;...

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

<span class="hljs-keyword">go</span>&#xA0;run&#xA0;.&#xA0;clone&#xA0;-h<br>Clone&#xA0;a&#xA0;repository&#xA0;into&#xA0;a&#xA0;<span class="hljs-built_in">new</span>&#xA0;directory<br><br>Usage:<br>&#xA0;&#xA0;git&#xA0;clone&#xA0;url&#xA0;[destination]&#xA0;[flags]<br><br>Flags:<br>&#xA0;&#xA0;-h,&#xA0;--help&#xA0;&#xA0;&#xA0;help&#xA0;<span class="hljs-keyword">for</span>&#xA0;clone

cobra 中选项分为两种.

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

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

var(<br>&#xA0;&#xA0;Verbose&#xA0;bool<br>)<br>func&#xA0;<span class="hljs-function"><span class="hljs-title">init</span></span>()&#xA0;{<br>&#xA0;&#xA0;rootCmd.PersistentFlags().BoolVarP(&Verbose,&#xA0;<span class="hljs-string">"verbose"</span>,&#xA0;<span class="hljs-string">"v"</span>,&#xA0;<span class="hljs-literal">false</span>,&#xA0;<span class="hljs-string">"verbose&#xA0;output"</span>)<br>}<br>

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

var(<br>&#xA0;&#xA0;Source&#xA0;bool<br>)<br>func&#xA0;<span class="hljs-function"><span class="hljs-title">init</span></span>()&#xA0;{<br>&#xA0;&#xA0;localCmd.Flags().StringVarP(&Source,&#xA0;<span class="hljs-string">"source"</span>,&#xA0;<span class="hljs-string">"s"</span>,&#xA0;<span class="hljs-string">""</span>,&#xA0;<span class="hljs-string">"Source&#xA0;directory&#xA0;to&#xA0;read&#xA0;from"</span>)<br>&#xA0;&#xA0;rootCmd.AddCommand(divideCmd)<br>}

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

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

<span class="hljs-keyword">go</span>&#xA0;install&#xA0;github.com/spf13/cobra-cli@latest

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

Usage:<br>&#xA0;&#xA0;cobra-cli&#xA0;init&#xA0;[path]&#xA0;[flags]<br><br>Aliases:<br>&#xA0;&#xA0;init,&#xA0;initialize,&#xA0;initialise,&#xA0;create<br><br>Flags:<br>&#xA0;&#xA0;-h,&#xA0;--help&#xA0;&#xA0;&#xA0;help&#xA0;<span class="hljs-keyword">for</span>&#xA0;init<br><br>Global&#xA0;Flags:<br>&#xA0;&#xA0;-a,&#xA0;--author&#xA0;<span class="hljs-keyword">string</span>&#xA0;&#xA0;&#xA0;&#xA0;author&#xA0;name&#xA0;<span class="hljs-keyword">for</span>&#xA0;copyright&#xA0;attribution&#xA0;(<span class="hljs-keyword">default</span>&#xA0;<span class="hljs-string">"YOUR&#xA0;NAME"</span>)<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;--config&#xA0;<span class="hljs-keyword">string</span>&#xA0;&#xA0;&#xA0;&#xA0;config&#xA0;file&#xA0;(<span class="hljs-keyword">default</span>&#xA0;is&#xA0;$HOME/.cobra.yaml)<br>&#xA0;&#xA0;-l,&#xA0;--license&#xA0;<span class="hljs-keyword">string</span>&#xA0;&#xA0;&#xA0;name&#xA0;of&#xA0;license&#xA0;<span class="hljs-keyword">for</span>&#xA0;the&#xA0;project<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;--viper&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;use&#xA0;Viper&#xA0;<span class="hljs-keyword">for</span>&#xA0;configuration
  • 根据提示子命令 init ,可选参数为 path
  • 选项为 -a 指定作者, --config string 指定 cobra-cli 自己的配置文件
  • -l 指定 license--viper 使用 viper 来读取配置文件。

使用 cobra init命令创建一个 cobra 应用程序:

$&#xA0;mkdir&#xA0;appname<br>$&#xA0;<span class="hljs-built_in">cd</span>&#xA0;appname<br>$&#xA0;cobra-cli&#xA0;init<br>Error:&#xA0;Please&#xA0;run&#xA0;go mod init <modname>&#xA0;before&#xA0;cobra-cli init<br>$&#xA0;go&#xA0;mod&#xA0;init<br>go:&#xA0;creating&#xA0;new&#xA0;go.mod:&#xA0;module&#xA0;github.com/golang-minibear2333/cmd_utils/git/appname<br>$&#xA0;cobra-cli&#xA0;init<br>Your&#xA0;Cobra&#xA0;application&#xA0;is&#xA0;ready&#xA0;at<br>/Users/xxxx/Documents/code/go/src/github.com/golang-minibear2333/cmd_utils/git/appname<br></modname>
  • 先初始化 mod 再初始化项目
  • 其中 appname 为应用程序名。生成的程序目录结构如下:
.<br>&#x251C;&#x2500;&#x2500;&#xA0;LICENSE<br>&#x251C;&#x2500;&#x2500;&#xA0;cmd<br>&#x2502;&#xA0;&#xA0;&#xA0;&#x2514;&#x2500;&#x2500;&#xA0;root.go<br>&#x251C;&#x2500;&#x2500;&#xA0;go.mod<br>&#x251C;&#x2500;&#x2500;&#xA0;go.sum<br>&#x2514;&#x2500;&#x2500;&#xA0;main.go

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

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

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

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

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

$&#xA0;cobra-cli&#xA0;init&#xA0;--viper<br>Your&#xA0;Cobra&#xA0;application&#xA0;is&#xA0;ready&#xA0;at<br>/Users/xxx/Documents/code/<span class="hljs-keyword">go</span>/src/github.com/golang-minibear2333/cmd_utils/git/cfg_load

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

$&#xA0;go&#xA0;run&#xA0;.&#xA0;--config==config/cfg.yaml

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

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

$&#xA0;cobra-cli&#xA0;add&#xA0;viperall

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

<span class="hljs-keyword">var</span>&#xA0;viperallCmd&#xA0;=&#xA0;&cobra.Command{<br>&#xA0;Use:&#xA0;&#xA0;&#xA0;<span class="hljs-string">"viperall"</span>,<br>&#xA0;Short:&#xA0;<span class="hljs-string">"Show&#xA0;cfg&#xA0;all"</span>,<br>&#xA0;Long:&#xA0;<span class="hljs-string">Show the contents of the entire configuration file</span>,<br>&#xA0;Run:&#xA0;<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cmd&#xA0;*cobra.Command,&#xA0;args&#xA0;[]<span class="hljs-keyword">string</span>)</span></span>&#xA0;{<br>&#xA0;&#xA0;fmt.Println(viper.AllSettings())<br>&#xA0;},<br>}

运行,妥了。

$&#xA0;go&#xA0;run&#xA0;.&#xA0;viperall&#xA0;--config=config/cfg.yaml&#xA0;<br>Using&#xA0;config&#xA0;file:&#xA0;config/cfg.yaml<br>map[people:map[age:18&#xA0;name:minibear2333]]

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

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

$&#xA0;cobra-cli&#xA0;add&#xA0;tt&#xA0;-p&#xA0;viperallCmd<br>tt&#xA0;created&#xA0;at&#xA0;/Users/xxx/Documents/code/go/src/github.com/golang-minibear2333/cmd_utils/git/cfg_load<br>$&#xA0;go&#xA0;run&#xA0;.&#xA0;viperall&#xA0;tt&#xA0;--config=config/cfg.yaml&#xA0;<br>Using&#xA0;config&#xA0;file:&#xA0;config/cfg.yaml<br>tt&#xA0;called
  • 注意父命令是 viperall ,但是 -p 指定的时候要改为 viperallCmd ,因为如下(我觉得这个是个很好的贡献pr,你可以建议作者改一下):
<span class="hljs-keyword">var</span>&#xA0;viperallCmd&#xA0;=&#xA0;&cobra.Command{
  • 每个 cobra 程序都有一个根命令,可以给它添加任意多个子命令。比如我们在 version.goinit 函数中将子命令添加到根命令中。
  • 创建子命令时指定子命令名称、子命令短提示、子命令调用的方法。
  • 三个重要概念,子命令、参数、选项。
  • 全局选项和子命令自己使用的选项。
  • cobra-cli 自动创建项目,自动创建配置文件读取项目,自动增加子命令,自动增加嵌套子命令。

推荐目录结构

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

  • 设置钩子函数,在命令执行前、后执行某些操作。
  • 生成 Markdown/ReStructed Text/Man Page 格式的文档。
  • 等。自己下来学咯。

cobra 库的使用非常广泛,很多知名项目都有用到,前面也提到过这些项目。学习这些项目是如何使用 cobra 的,可以从中学习 cobra 的特性和最佳实践。这也是学习开源项目的一个很好的途径。

Original: https://blog.csdn.net/BTnode/article/details/128717698
Author: 机智的程序员小熊
Title: 要命!我篡改了系统命令惊现事故,竟要扣我年终奖-Golang-cobra

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/811878/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球