Go 命令行解析 flag 包之快速上手

Go 命令行解析 flag 包之快速上手

本篇文章是 Go 標準庫 flag 包的快速上手篇。

概述

開發一個命令行工具,視複雜程度,一般要選擇一個合適的命令行解析庫,簡單的需求用 Go 標準庫 flag 就夠了,flag 的使用非常簡單。

當然,除了標準庫 flag 外,也有不少的第三方庫。比如,為了替代 flag 而生的 pflag[1],它支持 POSIX 風格的命令行解析。關於 POSIX 風格,本文末尾有個簡單的介紹。

更多與命令行處理相關的庫,可以打開 awesome-go#command-line[2] 命令行一節查看,star 最多的是 spf13/cobra[3]urfave/cli[4] ,與 flag / pflag 相比,它們更加複雜,是一個完全的全功能的框架。

有興趣都可以瞭解下。

目標案例

迴歸主題,繼續介紹 flag 吧。通過案例介紹包的使用會比較直觀。

舉一個例子說明吧。假設,現在要開發一個 Go 語言環境的版本管理工具,gvg(go version management by go)。

命令行的幫助信息如下:

NAME:
 gvg - go version management by go

USAGE:
 gvg [global options] command [command options] [arguments...]

VERSION:
 0.0.1

COMMANDS:
 list list go versions
 install install a go version
 info show go version info
 use select a version
 uninstall uninstall a go version
 get get the latest code
 uninstall uninstall a go version
 help, h Shows a list of commands or help for one command

GLOBAL OPTIONS:
 --help, -h show help
 --version, -v print the version

這個命令不僅包含了全局的選項,還有 8 個子命令,部分子命令支持參數和選項。暫時,子命令的選項參數先不列出來了,實現時再看。

接下來,我們試著通過 flag 實現這個效果。本文只介紹 GLOBAL OPTIONS(全局選項)的實現。

如果想了解什麼是 Go 語言環境的版本管理,可以查看 如何靈活地進行 Go 版本管理 一文。

選項表示

最簡單的命令不需要任何參數和選項,複雜一點,要支持參數和選項的配置。gvg 沒有全局參數,或者說全局參數是子命令,全局選項有 --help -h 和 --version -h。

一個選項在 flag 包中用一個 Flag 表示,那 -h 可以用一個 Flag 表示。一個選項通常由幾個部分組成,如名稱、使用說明和默認值。如果將 -h 用代碼表示,如下:

h := flag.Bool("h", false, "show help")

定義了一個布爾類型的 Flag,名為 h,默認值是 false,使用說明為 "show help"。變量 h 是一個布爾型的指針,通過它可以取出命令行傳入的值。

除了使用 flag.Bool,還可以使用另外一種方式定義一個 Flag。我們可以用這種方式定義 -v 選項。

代碼如下:

var v bool
flag.BoolVar(&v, "v", false, "print the version")

最後的三個參數含義與 flag.Bool 相同,主要區別在值的獲取方式,flag.BoolVar 是通過將變量地址傳入獲取值。從經驗來看,第二種方式使用的較多,或許因為第一種方式會發生變量逃逸。

更多類型

除了布爾類型,Flag 的類型還有整數(int、int64、uint、uint64)、浮點數(float64)、字符串(string)和時長(time.Duration)。

假設 gvg 的案例中,支持配置文件選項 --config-path。實現代碼如下:

var configPath

flag.StringVar(&configPath, "config-path", "", "config file path")

通過 StringVar 定義了新的 Flag。使用方式與 BoolVar 相同,最後的三個參數分別是選項名稱、默認值和使用說明。

雖然 flag 支持的內置類型並不多,但已經滿足大部分需求了。如果有自定義的需求,也可以擴展新的類型實現,這部分內容下篇介紹。

長短選項

現在已經完成了 -h 和 -v 兩個選項,但目標是 -v --version 和 -h --help,即同時支持長短選項。

一個 Flag 應該有長短兩種形式,但 flag 包並不支持這種風格,需要曲線救國才能實現。(注:本文開開頭提到的 pflag 支持。)

這裡以 -v --version 為例,代碼如下:

flag.BoolVar(&v, "v", false, "print the version")
flag.BoolVar(&v, "version", false, "print the version")

定義了兩個 Flag,同時綁定到了一個變量上。這種效果只能用 flag.BoolVar 方式定義新的 Flag,flag.Bool 沒辦法做到將同一個變量同時綁定兩個 Flag。

但其實這種也有缺點,先不說了,後面介紹幫助信息打印時就明白了。

命令行解析

定義好所有 Flag,還需要一步解析才能拿到正確的結果。這一步非常簡單,調用 flag.Parse() 即可。

如下是完整的代碼:

package main

var h *bool
var v bool

func init() {
	flag.BoolVar(&h, "h", false, "show help")
	flag.BoolVar(&h, "help", false, "show help")
	flag.BoolVar(&v, "v", false, "print the version")
	flag.BoolVar(&v, "version", false, "print the version")
}

func main() {
	flag.Parse()
	fmt.Println("version", v)
	fmt.Println("help", h)
}

現在就可以將它編譯為 gvg 命令了。

使用命令

在正式使用命令前,先介紹下 flag 的語法。官方文檔說明,命令行中 flag 選項的使用語法有如下幾種形式。

-flag
-flag=x
-flag x // 非布爾類型才支持這種方式

但其實,-- 也是支持的。因此,上面才可以實現 --version 的曲線救國。

使用下這個命令,將 help 設置為 false 和 version 設置為 true。我儘量把所有可能的寫法都列出來。

$ gvg -v
$ gvg -version -h=false # 單個 - ,即 -version 支持
$ gvg --version=true --help=false
$ gvg --version=1 --help=0
$ gvg --version=t --help=f
$ gvg --version=T --help=F
$ gvg --version true --help true # 寫法錯誤,因為無法識別出是 bool 值,還是參數或子命令
$ gvg -vh # 不支持這種風格

執行命令,輸出結果:

version true
help false

到這裡,flag 的快速入門就介紹完了。參數留在子命令的時候介紹。

命令行風格

由於一些歷史原因,Unix 出現過很多不同的分支,命令行的風格也因此有很多標準,比如:

  • Unix 風格,選項採用單 - 加一個字母,比如 -v,短選項就是它,優點是足夠簡潔;
  • BSD 風格,選項沒有 -,沒有任何的前綴,不知道有參數的情況怎麼處理,沒有研究;
  • GNU 風格,採用 --,如 --version,長選項,擴展性好,但是要多打幾個字母;

在網上找到一個搞笑漫畫。

Go 命令行解析 flag 包之快速上手

查看系統進程有兩種寫法, ps aux(BSD 風格) 和 ps -elf(Unix 風格)。之前,我一直很鬱悶為什麼有這個區別。現在算是明白了。哈哈。

POSIX 的命令行風格算是取長補短的集合吧。什麼是 POSIX 風格?可以查看這篇文檔 命令參數語法[5]。它同時提供了長短選項的標準。

要明白的是,標準終究只是標準,很多命令其實並不遵循它。但自己在設計命令行規範的時候,最好還是要有一套標準,而參考最統一的標準肯定是沒錯的。

總結

本文介紹了 Go 中 flag 包的使用,一般的場景已經足夠使用了。最後,簡單地談了一個比較趣味性的話題,命令行的風格,是否有種感覺,程序員之間的門派之爭真是無處不在。

參考資料

[1]

pflag: github.com/spf13/pflag

[2]

awesome-go#command-line: https://awesome-go.com/#command-line

[3]

spf13/cobra: https://github.com/spf13/cobra

[4]

urfave/cli: https://github.com/urfave/cli

[5]

命令參數語法: http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html


分享到:


相關文章: