Cobra 庫上手—自建命令列工具

FunTester發表於2024-08-08

Cobra 簡介

Cobra 是一個流行的 Go 語言庫,用於建立強大且靈活的命令列應用程式。它由 spf13 開發,設計用於與 Go 生態系統中的其他流行庫(如 Viper 配置庫)無縫整合。Cobra 支援多級命令結構,允許定義根命令和任意數量的子命令,還可以輕鬆處理全域性和本地標誌。它自動生成幫助和使用資訊,並支援 BashZshFishPowerShell 的命令補全。此外,Cobra 能夠生成 Markdown 格式的文件,使文件維護更加便捷。透過與 Viper 整合,Cobra 能處理配置檔案和環境變數,為開發者提供了強大的工具集,使建立複雜的 Client 工具變得簡單高效。Cobra 廣泛應用於各種 Go 專案中,提升了 Client 應用的開發體驗和維護效率。

下面是 Cobra 主要的功能:

  • 支援多級命令結構:輕鬆定義根命令和任意數量的子命令,組織複雜的命令列應用。
  • 處理全域性和本地標誌:解析和處理命令列引數,支援全域性標誌和區域性標誌。
  • 自動生成幫助和使用資訊:根據命令和標誌自動生成詳細的幫助和使用資訊。
  • 支援命令補全:提供 Bash、Zsh、Fish 和 PowerShell 的命令補全指令碼,提升使用者體驗。
  • 生成 Markdown 格式的文件:自動生成命令列工具的 Markdown 格式文件,方便文件維護。

Cobra 的使用非常廣泛,特別是在雲原生和 DevOps 工具中。下面是使用 Cobra 構建 CLI 工具的知名工具:

  • Kubernetes (k8s):這個流行的容器編排平臺的命令列工具 kubectl 就是使用 Cobra 構建的。
  • Docker:Docker CLI 也是基於 Cobra 開發的。
  • Hugo:這個流行的靜態網站生成器使用 Cobra 來構建其命令列介面。
  • GitHub CLI:GitHub 的官方命令列工具 gh 也使用了 Cobra。
  • CoreDNS:這個靈活可擴充套件的 DNS 伺服器使用 Cobra 來管理其命令列介面。

下面讓我們一起進入正題,Cobra 上手教程。

Cobra 上手教程

第一步我們要建立一個專案,然後為專案安裝 Cobra

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

我們在 main.go 中新增如下程式碼來建立第一個命令列工具:

package main  

import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  

func main() {  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,列印 FunTester !!!",  
       Long:  "Demo of Cobra,列印 FunTester !!!,使用Cobra構建命令列工具,實現簡單的命令列功能",  
       Run: func(cmd *cobra.Command, args []string) {  
          fmt.Println("Hello, FunTester!")  
       },  
    }  

    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}

這是引數的解釋:

  • Use: 命令的使用方法,通常是命令的名稱。
  • Short: 簡短描述,在幫助資訊中顯示。
  • Long: 詳細描述,在幫助資訊中顯示。
  • Run: 命令執行時的函式。

我們本地編譯執行 ./funtester ,控制檯就會列印

Hello, FunTester!

接收引數

Cobra 中,處理命令列引數是構建命令列應用的核心部分。Cobra 提供了多種方式來接收和處理引數,包括標誌(flags)和位置引數(arguments)。

flags 引數

標誌是用於提供額外資訊的引數,通常以 --flag=value 或 -f value 形式出現。Cobra 支援全域性標誌和本地標誌,標誌可以是布林型、整數型、浮點型或字串型。

我們可以在原來的程式中,建立 funCmd 之後新增這麼一段程式碼:

// 新增命令列引數,並設定預設值,使用StringVarP方法,第一個引數是指標,第二個引數是命令列引數名稱,第三個引數是命令列引數的簡寫,第四個引數是預設值,第五個引數是命令列引數的描述  
funCmd.Flags().StringP("name", "n", "FunTester", "姓名,用於列印")

然後我們在引數 Run 引數中這麼處理:

Run: func(cmd *cobra.Command, args []string) {  
    name, err := cmd.Flags().GetString("name")  
    if err == nil {  
       fmt.Printf("Hello, %s ! \n", name)  
    } else {  
       fmt.Println(fmt.Sprintln(err))  
    }  
},

這樣我們就可以接收引數了,使用方法如下:

╰─⠠⠵ ./funtester --name FunTester001
Hello, FunTester001 ! 
╰─⠠⠵ ./funtester -n FunTester002
Hello, FunTester002 ! 

同樣地,Cobra 還提供了整型和浮點型資料接收和獲取方法,這裡就不一一展示了。完整程式碼如下:

package main  

import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  

func main() {  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,列印 FunTester !!!",  
       Long:  "Demo of Cobra,列印 FunTester !!!,使用Cobra構建命令列工具,實現簡單的命令列功能",  
       Run: func(cmd *cobra.Command, args []string) {  
          name, err := cmd.Flags().GetString("name")  
          if err == nil {  
             fmt.Printf("Hello, %s age: %s ! \n", name, age)  
          } else {  
             fmt.Println(fmt.Sprintln(err))  
          }  
       },  
    }  
    // 新增命令列引數,並設定預設值,使用StringVarP方法,第一個引數是指標,第二個引數是命令列引數名稱,第三個引數是命令列引數的簡寫,第四個引數是預設值,第五個引數是命令列引數的描述  
    funCmd.Flags().StringP("name", "n", "FunTester", "姓名,用於列印")  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}

如果我們想直接使用當前的屬性或者物件接收引數值,我們可以用下面的方法:

package main  

import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  

func main() {  
    var name string  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,列印 FunTester !!!",  
       Long:  "Demo of Cobra,列印 FunTester !!!,使用Cobra構建命令列工具,實現簡單的命令列功能",  
       Args:  cobra.ExactArgs(1), // 限制引數個數,只能有一個引數,否則報錯  
       Run: func(cmd *cobra.Command, args []string) {  
          fmt.Printf("Hello, %s ! \n", name)  
       },  
    }  
    // 新增命令列引數,並設定預設值,和提示資訊,以及引數的簡寫,和引數的使用說明  
    funCmd.Flags().StringVarP(&name, "name", "n", "FunTester", "姓名,用於列印")  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}

如果我們定義一些必傳引數,可以用下面的語法:

funCmd.MarkFlagRequired("name")

位置引數

位置引數是命令列中緊跟在命令後面的引數,它們不以 --flag 的形式出現。位置引數可以是必需的,也可以是可選的。

我們在上一版程式碼的基礎上新增新的程式碼,增加了 Args 引數控制位置引數的個數,然後在 Run 引數中處理位置引數。完整程式碼如下:

package main  

import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  

func main() {  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,列印 FunTester !!!",  
       Long:  "Demo of Cobra,列印 FunTester !!!,使用Cobra構建命令列工具,實現簡單的命令列功能",  
       Args:  cobra.ExactArgs(1), // 限制引數個數,只能有一個引數,否則報錯  
       Run: func(cmd *cobra.Command, args []string) {  
          age := args[0]  
          name, err := cmd.Flags().GetString("name")  
          if err == nil {  
             fmt.Printf("Hello, %s age: %s ! \n", name, age)  
          } else {  
             fmt.Println(fmt.Sprintln(err))  
          }  
       },  
    }  
    // 新增命令列引數,並設定預設值,使用StringVarP方法,第一個引數是指標,第二個引數是命令列引數名稱,第三個引數是命令列引數的簡寫,第四個引數是預設值,第五個引數是命令列引數的描述  
    funCmd.Flags().StringP("name", "n", "FunTester", "姓名,用於列印")  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}

下面我們來編譯執行看看。

╰─⠠⠵ ./funtester 123 --name FunTester002
Hello, FunTester002 age: 123 ! 
╰─⠠⠵ ./funtester  --name FunTester002 3242
Hello, FunTester002 age: 3242 ! 
╰─⠠⠵ ./funtester --name FunTester002    
Error: accepts 1 arg(s), received 0
Usage:
  fun [flags]

Flags:
  -h, --help          help for fun
  -n, --name string   姓名,用於列印 (default "FunTester")

2024/07/26 15:21:23 accepts 1 arg(s), received 0

可以看出,位置引數無論在前還是在後,都不影響,但是如果缺少,就會報錯。不得不說,Cobra 相容性還不錯。

如果我們位置引數數量不固定,對於引數的要求也比較多,可以參考 cobra.PositionalArgs 來解決,這裡定義:

type PositionalArgs func(cmd *Command, args []string) error

下面是 Cobra 提供的幾種實現型別:

在 Cobra 中,PositionalArgs 是一個用於定義和驗證命令列位置引數的型別。它允許你指定命令的引數數量和驗證規則。以下是幾種常見的 PositionalArgs 驗證函式以及如何使用它們的示例:

常見的 PositionalArgs 驗證函式

  1. cobra.NoArgs:不接受任何位置引數。
  2. cobra.ArbitraryArgs:接受任意數量的位置引數。
  3. cobra.ExactArgs(n int):接受確切數量的引數。
  4. cobra.MinimumNArgs(n int):接受至少 n 個引數。
  5. cobra.MaximumNArgs(n int):接受最多 n 個引數.
  6. cobra.RangeArgs(min, max int):接受引數數量在指定範圍內(包括邊界)。

示例程式碼

以下是一些使用 PositionalArgs 驗證函式的示例:

  • cobra.NoArgs: 不接受任何位置引數。
  • cobra.ArbitraryArgs: 接受任意數量的位置引數。
  • cobra.ExactArgs(n int): 接受確切數量的引數。
  • cobra.MinimumNArgs(n int): 接受至少 n 個引數。
  • cobra.MaximumNArgs(n int): 接受最多 n 個引數。
  • cobra.RangeArgs(min, max int): 接受引數數量在指定範圍內。

這些驗證函式使得處理和驗證命令列引數變得更加簡單和直觀。根據應用的需求選擇合適的 PositionalArgs 函式,可以確保使用者提供正確數量和型別的引數,從而提高應用的可靠性和使用者體驗。

全域性共享標誌

Cobra 中,PersistentFlags 是用於在所有命令(包括子命令)中共享的標誌。PersistentFlags 通常用於定義全域性設定或配置引數,這些引數在所有命令中都可以使用。

使用方法如下:

// 為命令新增引數,預設值為FunTester  
funCmd.PersistentFlags().StringP("name", "n", "FunTester", "姓名,用於列印")

這個方法通常在構建子命令的時候用到,在父命令新增標誌,所有的子命令也同時會擁有同樣的標識。

子命令

在 Cobra 中,子命令(Subcommands)是命令列工具的一部分,使得工具能夠包含多個操作或功能,每個子命令都可以有自己的標誌和引數。子命令讓命令列工具更加模組化和靈活。

下面是一個簡單的例子:

package main  

import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  

func main() {  
    var name string  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,列印 FunTester !!!",  
       Long:  "Demo of Cobra,列印 FunTester !!!,使用Cobra構建命令列工具,實現簡單的命令列功能",  
       Run: func(cmd *cobra.Command, args []string) {  
          fmt.Printf("Hello, %s ! \n", name)  
       },  
    }  
    // 為命令新增引數,預設值為FunTester  
    funCmd.PersistentFlags().StringVarP(&name, "name", "n", "FunTester", "姓名,用於列印")  
    funCmd.MarkFlagRequired("name") // 標記為必須引數  
    sub := &cobra.Command{  
       Use:   "sub",  
       Short: "sub command",  
       Run: func(cmd *cobra.Command, args []string) {  
          fmt.Println("sub command print: " + name)  
       },  
    }  
    funCmd.AddCommand(sub)  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}

使用方法如下:

╰─⠠⠵ ./funtester sub --name FunTester002 32432
sub command print: FunTester002

其他

Cobra 其他一些高階功能,暫時還用不到,有興趣的可以自行研究研究。PS:要生成 Markdown 文件並將其轉換為 man 頁面格式,可以使用 github.com/cpuguy83/go-md2man/v2/md2man 包。也可以使用 --help 來獲取自動生成的提示資訊。

FunTester 原創精華
  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go、Python
  • 單元&白盒&工具合集
  • 測試方案&BUG&爬蟲&UI
  • 測試理論雞湯
  • 社群風采&影片合集
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章