網站首頁 編程語言 正文
雖然現在我們使用的大多數軟件都是可視化的,很容易上手,但是這并不代表 CLI(命令行)應用就沒有用武之地了,特別是對于開發人員來說,還是會經常和 CLI 應用打交道。而 Golang 就非常適合用來構建 CLI 應用,下面我們就將來介紹如何在 Golang 中構建一個 CLI 應用。
對于開發人員來說平時可能就需要使用到很多 CLI 工具,比如 npm、node、go、python、docker、kubectl 等等,因為這些工具非常小巧、沒有依賴性、非常適合系統管理或者一些自動化任務等等。
我們這里選擇使用 Golang 里面非常有名的Cobra庫來進行 CLI 工具的開發。Cobra 是一個功能強大的現代化 CLI 應用程序庫,有很多知名的 Go 項目使用 Cobra 進行構建,比如:Kubernetes、Docker、Hugo 等等
概念
Cobra 是構建在命令、參數和標識符之上的:
-
Commands
表示執行動作 -
Args
就是執行參數 -
Flags
是這些動作的標識符
基本的執行命令如下所示:
$ APPNAME Command Args --Flags # 或者 $ APPNAME Command --Flags Args
比如我們平時使用的一些命令行工具:
git clone URL -bare go get -u URL npm install package –save kubectl get pods -n kube-system -l app=cobra
示例
下面我們來看下 Cobra 的使用,這里我們使用的 go1.13.3 版本,使用 Go Modules 來進行包管理,如果對這部分知識點不熟悉的,可以查看前面我們的文章Go Modules 基本使用(視頻)了解。
新建一個名為my-calc
的目錄作為項目目錄,然后初始化 modules:
$ mkdir my-calc && cd my-calc # 如果 go modules 默認沒有開啟,需要執行 export GO111MODULE=on 開啟 $ go mod init my-calc go: creating new go.mod: module my-calc
初始化完成后可以看到項目根目錄下面多了一個go.mod
的文件,現在我們還沒有安裝cobra
庫,執行下面的命令進行安裝:
# 強烈推薦配置該環境變量 $ export GOPROXY=https://goproxy.cn $ go get -u github.com/spf13/cobra/cobra
安裝成功后,現在我們可以使用cobra init
命令來初始化 CLI 應用的腳手架:
$ cobra init --pkg-name my-calc Your Cobra applicaton is ready at /Users/ych/devs/workspace/youdianzhishi/course/my-calc
需要注意的是新版本的 cobra 庫需要提供一個--pkg-name
參數來進行初始化,也就是指定上面我們初始化的模塊名稱即可。上面的 init 命令就會創建出一個最基本的 CLI 應用項目:
$ tree . . ├── LICENSE ├── cmd │ └── root.go ├── go.mod ├── go.sum └── main.go 1 directory, 5 files
其中main.go
是 CLI 應用的入口,在main.go
里面調用好了cmd/root.go
下面的Execute
函數:
// main.go package main import "my-calc/cmd" func main() { cmd.Execute() }
然后我們再來看下cmd/root.go
文件。
rootCmd
root(根)命令是 CLI 工具的最基本的命令,比如對于我們前面使用的go get URL
,其中go
就是 root 命令,而get
就是go
這個根命令的子命令,而在root.go
中就直接使用了 cobra 命令來初始化rootCmd
結構,CLI 中的其他所有命令都將是rootCmd
這個根命令的子命令了。
這里我們將cmd/root.go
里面的rootCmd
變量內部的注釋去掉,并在Run
函數里面加上一句fmt.Println("Hello Cobra CLI")
:
var rootCmd = &cobra.Command{ Use: "my-calc", Short: "A brief description of your application", Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Hello Cobra CLI") }, }
這個時候我們在項目根目錄下面執行如下命令進行構建:
$ go build -o my-calc
該命令會在項目根目錄下生成一個名為my-calc
的二進制文件,直接執行這個二進制文件可以看到如下所示的輸出信息:
$ ./my-calc
Hello Cobra CLI
init
我們知道init
函數是 Golang 中初始化包的時候第一個調用的函數。在cmd/root.go
中我們可以看到init
函數中調用了cobra.OnInitialize(initConfig)
,也就是每當執行或者調用命令的時候,它都會先執行init
函數中的所有函數,然后再執行execute
方法。該初始化可用于加載配置文件或用于構造函數等等,這完全依賴于我們應用的實際情況。
在初始化函數里面cobra.OnInitialize(initConfig)
調用了initConfig
這個函數,所有,當rootCmd
的執行方法RUN: func
運行的時候,rootCmd
根命令就會首先運行initConfig
函數,當所有的初始化函數執行完成后,才會執行rootCmd
的RUN: func
執行函數。
我們可以在initConfig
函數里面添加一些 Debug 信息:
func initConfig() { fmt.Println("I'm inside initConfig function in cmd/root.go") ... }
然后同樣重新構建一次再執行:
$ go build -o my-calc $ ./my-calc I'm inside initConfig function in cmd/root.go Hello Cobra CLI
可以看到是首先運行的是initConfig
函數里面的信息,然后才是真正的執行函數里面的內容。
為了搞清楚整個 CLI 執行的流程,我們在main.go
里面也添加一些 Debug 信息:
// cmd/root.go func init() { fmt.Println("I'm inside init function in cmd/root.go") cobra.OnInitialize(initConfig) ... } func initConfig() { fmt.Println("I'm inside initConfig function in cmd/root.go") ... } // main.go func main() { fmt.Println("I'm inside main function in main.go") cmd.Execute() }
然后同樣重新構建一次再執行:
$ go build -o my-calc $ ./my-calc I'm inside init function in cmd/root.go I'm inside main function in main.go I'm inside initConfig function in cmd/root.go Hello Cobra CLI
根據上面的日志信息我們就可以了解到 CLI 命令的流程了。
init
函數最后處理的就是flags
了,Flags
就類似于命令的標識符,我們可以把他們看成是某種條件操作,在 Cobra 中提供了兩種類型的標識符:Persistent Flags
和Local Flags
。
-
Persistent Flags
: 該標志可用于為其分配的命令以及該命令的所有子命令。 -
Local Flags
: 該標志只能用于分配給它的命令。
initConfig
該函數主要用于在 home 目錄下面設置一個名為.my-calc
的配置文件,如果該文件存在則會使用這個配置文件。
// cmd/root.go // initConfig 讀取配置文件和環境變量 func initConfig() { if cfgFile != "" { // 使用 flag 標志中傳遞的配置文件 viper.SetConfigFile(cfgFile) } else { // 獲取 Home 目錄 home, err := homedir.Dir() if err != nil { fmt.Println(err) os.Exit(1) } // 在 Home 目錄下面查找名為 ".my-calc" 的配置文件 viper.AddConfigPath(home) viper.SetConfigName(".my-calc") } // 讀取匹配的環境變量 viper.AutomaticEnv() // 如果有配置文件,則讀取它 if err := viper.ReadInConfig(); err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) } }
viper
是一個非常優秀的用于解決配置文件的 Golang 庫,它可以從 JSON、TOML、YAML、HCL、envfile 以及 Java properties 配置文件中讀取信息,功能非常強大,而且不僅僅是讀取配置這么簡單,了解更多相關信息可以查看 Git 倉庫相關介紹:https://github.com/spf13/viper。
現在我們可以去掉前面我們添加的一些打印語句,我們已經創建了一個my-calc
命令作為rootCmd
命令,執行該根命令會打印Hello Cobra CLI
信息,接下來為我們的 CLI 應用添加一些其他的命令。
添加數據
在項目根目錄下面創建一個名為add
的命令,Cobra
添加一個新的命令的方式為:cobra add <commandName>
,所以我們這里直接這樣執行:
$ cobra add add add created at /Users/ych/devs/workspace/youdianzhishi/course/my-calc $ tree . . ├── LICENSE ├── cmd │ ├── add.go │ └── root.go ├── go.mod ├── go.sum ├── main.go └── my-calc 1 directory, 7 files
現在我們可以看到cmd/root.go
文件中新增了一個add.go
的文件,我們仔細觀察可以發現該文件和cmd/root.go
比較類似。首先是聲明了一個名為addCmd
的結構體變量,類型為*cobra.Command
指針類型,*cobra.Command
有一個RUN
函數,帶有*cobra.Command
指針和一個字符串切片參數。
然后在init
函數中進行初始化,初始化后,將其添加到rootCmd
根命令中rootCmd.AddCommand(addCmd)
,所以我們可以把addCmd
看成是rootCmd
的子命令。
同樣現在重新構建應用再執行:
$ go build -o my-calc $ ./my-calc Hello Cobra CLI $ ./my-calc add add called
可以看到add
命令可以正常運行了,接下來我們來讓改命令支持添加一些數字,我們知道在RUN
函數中是用戶字符串 slice 來作為參數的,所以要支持添加數字,我們首先需要將字符串轉換為 int 類型,返回返回計算結果。在cmd/add.go
文件中添加一個名為intAdd
的函數,定義如下所示:
// cmd/add.go func intAdd(args []string) { var sum int // 循環 args 參數,循環的第一個值為 args 的索引,這里我們不需要,所以用 _ 忽略掉 for _, ival := range args { // 將 string 轉換成 int 類型 temp, err := strconv.Atoi(ival) if err != nil { panic(err) } sum = sum + temp } fmt.Printf("Addition of numbers %s is %d\n", args, sum) }
然后在addCmd
變量中,更新RUN
函數,移除默認的打印信息,調用上面聲明的addInt
函數:
// addCmd Run: func(cmd *cobra.Command, args []string) { intAdd(args) },
然后重新構建應用執行如下所示的命令:
$ go build -o my-calc $ ./my-calc Hello Cobra CLI # 注意參數之間的空格 $ ./my-calc add 1 2 3 Addition of numbers [1 2 3] is 6
由于RUN
函數中的args
參數是一個字符串切片,所以我們可以傳遞任意數量的參數,但是確有一個缺陷,就是只能進行整數計算,不能計算小數,比如我們執行如下的計算就會直接 panic 了:
$ ./my-calc add 1 2 3.5 panic: strconv.Atoi: parsing "3.5": invalid syntax goroutine 1 [running]: my-calc/cmd.intAdd(0xc0000a5890, 0x3, 0x3) ......
因為在intAdd
函數里面,我們只是將字符串轉換成了 int,而不是 float32/64 類型,所以我們可以為addCmd
命令添加一個flag
標識符,通過該標識符來幫助 CLI 確定它是 int 計算還是 float 計算。
在cmd/add.go
文件的init
函數內部,我們創建一個 Bool 類型的本地標識符,命名成float
,簡寫成f
,默認值為 false。這個默認值是非常重要的,意思就是即使沒有在命令行中調用 flag 標識符,該標識符的值就將為 false。
// cmd/add.go func init() { rootCmd.AddCommand(addCmd) addCmd.Flags().BoolP("float", "f", false, "Add Floating Numbers") }
然后創建一個floatAdd
的函數:
func floatAdd(args []string) { var sum float64 for _, fval := range args { // 將字符串轉換成 float64 類型 temp, err := strconv.ParseFloat(fval, 64) if err != nil { panic(err) } sum = sum + temp } fmt.Printf("Sum of floating numbers %s is %f\n", args, sum) }
該函數和上面的intAdd
函數幾乎是相同的,除了是將字符串轉換成 float64 類型。然后在addCmd
的RUN
函數中,我們根據傳入的標識符來判斷到底應該是調用intAdd
還是floatAdd
,如果傳遞了--float
或者-f
標志,就將會調用floatAdd
函數。
// cmd/add.go // addCmd Run: func(cmd *cobra.Command, args []string) { // 獲取 float 標識符的值,默認為 false fstatus, _ := cmd.Flags().GetBool("float") if fstatus { // 如果為 true,則調用 floatAdd 函數 floatAdd(args) } else { intAdd(args) } },
現在重新編譯構建 CLI 應用,按照如下方式執行:
$ go build -o my-calc $ ./my-calc add 1 2 3 Addition of numbers [1 2 3] is 6 $ ./my-calc add 1 2 3.5 -f Sum of floating numbers [1 2 3.5] is 6.500000 $./my-calc add 1 2 3.5 --float Sum of floating numbers [1 2 3.5] is 6.500000
然后接下來我們在給addCmd
添加一些子命令來擴展它。
添加偶數
同樣在項目根目錄下執行如下命令添加一個名為even
的命令:
$ cobra add even even created at /Users/ych/devs/workspace/youdianzhishi/course/my-calc
和上面一樣會在root
目錄下面新增一個名為even.go
的文件,修改該文件中的init
函數,將rootCmd
修改為addCmd
,因為我們是為addCmd
添加子命令:
// cmd/even.go func init() { addCmd.AddCommand(evenCmd) }
然后更新evenCmd
結構體參數的RUN
函數:
// cmd/even.go Run: func(cmd *cobra.Command, args []string) { var evenSum int for _, ival := range args { temp, _ := strconv.Atoi(ival) if temp%2 == 0 { evenSum = evenSum + temp } } fmt.Printf("The even addition of %s is %d\n", args, evenSum) },
首先將字符串轉換成整數,然后判斷如果是偶數才進行累加。然后重新編譯構建應用:
$ go build -o my-calc $ ./my-calc add even 1 2 3 4 5 6 The even addition of [1 2 3 4 5 6] is 12
my-calc
是我們的根命令,add
是rootCmd
的子命令,even
優勢addCmd
的子命令,所以按照上面的方式調用。可以用同樣的方式再去添加一個奇數相加的子命令。
到這里我們就在 Golang 里面使用Cobra
創建了一個簡單的 CLI 應用。本文的內容雖然比較簡單,但是是我們了解學習Cobra
基礎的一個很好的入門方式,后續我們也可以嘗試添加一些更加復雜的使用案例。
Reference:
https://www.qikqiak.com/post/create-cli-app-with-cobra在 Golang 中使用 Cobra 創建 CLI 應用
https://cobra.dev cobra docs
原文鏈接:https://www.cnblogs.com/piperck/p/15762355.html
相關推薦
- 2022-12-29 React點擊事件的兩種寫法小結_React
- 2022-09-06 React父組件調用子組件中的方法實例詳解_React
- 2022-06-11 python實現自動整理文件_python
- 2023-03-28 python程序中調用其他程序的實現_python
- 2021-12-27 readAsText 讀取本地文件
- 2022-09-17 python?df遍歷的N種方式(小結)_python
- 2022-11-09 GO?語言運行環境的基礎知識_Golang
- 2022-08-05 springBoot集成swagger啟動報錯:Failed to start bean ‘docu
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支