Golang開發命令列工具之flag包的使用
-
- 3、flag包命令列引數的定義
- 4、flag包命令列引數解析
- 6、flag定義短引數和長引數
1、命令列工具概述
日常命令列操作,相對應的眾多命令列工具是提高生產力的必備工具,我在之前的文章 我的生產力工具推薦-終端01篇 中有推薦過一些我常用的基於 terminal
終端的命令列 cli
工具
滑鼠能夠讓使用者更容易上手,降低使用者學習成本。 而對於開發者,鍵盤操作模式能顯著提升生產力,還有在一些專業工具中, 大量使用快捷鍵代替繁瑣的滑鼠操作,能夠使開發人員更加專注於工作,提高效率,因為鍵盤操作模式更容易產生肌肉記憶
舉個栗子。我司業務研發,前些年在我們的強力推動下(被迫)轉向使用了 git
作為版本控制,開始使用的是圖形化“小烏龜”工具。後續出現幾次問題解決起來較麻煩後,推薦其使用原生的 git
命令列。如今,使用 git
命令列操作版本控制可謂 “一頓操作猛如虎......”
命令列(鍵盤)操作在很大程度上可以提高工作效率,與之相對應的是滑鼠(觸屏等)操作,這兩種模式是目前的主流人機互動方式
設計一款命令列工具的開發語言可以選擇原始的 shell
、甚至是更原始的語言 C
,更為容易上手且功能更多的有 node
、 python
、 golang
本文是基於 golang
開發命令列工具的開篇,主要是基於 golang
原生內建的、輕量的 flag
包實現,用 golang
設計命令列工具而不用 shell
、 python
的原因這裡就不做論述了
2、flag包介紹
flag
包用來解析命令列引數
相比簡單的使用 os.Args
來獲取命令列引數, flag
可以實現按照更為通用的命令列用法,例如 mysql -u root -p 123456
。其中 mysql
是命令列的名稱即這個命令, -u
和 -p
分別是這個命令的兩個引數:使用者名稱和密碼,後面接著的是對應的引數值,有了引數的宣告之後,兩個引數可以互換位置,引數值也可以選填或按照預設(預設)值進行指定
flag
的詳細用法可參考 flag包文件
flag
包支援的命令列引數的型別有 bool
、 int
、 int64
、 uint
、 uint64
、 float
float64
、 string
、 duration
即布林值、整型、浮點型、字串、時間段型別
3、flag包命令列引數的定義
定義 flag
命令列引數,用來接收命令列輸入的引數值,一般有以下兩種方法
- flag.TypeVar():先定義引數(實際上是指標),再定義
flag.TypeVar
將命令列引數儲存(繫結)到前面引數的值的指標(地址)
var name string var age int var height float64 var graduated bool // &name 就是接收使用者命令列中輸入的-n後面的引數值 // 返回值是一個用來儲存name引數的值的指標/地址 // 定義string型別命令列引數name,括號中依次是變數名、flag引數名、預設值、引數說明 flag.StringVar(&name, "n", "", "name引數,預設為空") // 定義整型命令列引數age flag.IntVar(&age,"a", 0, "age引數,預設為0") // 定義浮點型命令列引數height flag.Float64Var(&height,"h", 0, "height引數,預設為0") // 定義布林型命令列引數graduated flag.BoolVar(&graduated,"g", false, "graduated引數,預設為false")
- flag.Type():用短變數宣告的方式定義引數型別及變數名
// 定義string型別命令列引數name,括號中依次是flag引數名、預設值、引數說明 namePtr := flag.String("n", "", "name引數,預設為空") // 定義整型命令列引數age age := flag.Int("a", 0, "age引數,預設為0") // 定義浮點型命令列引數height height := flag.Float64("h", 0, "height引數,預設為0") // 定義布林型命令列引數graduated graduated:= flag.Bool("g", false, "graduated引數,預設為false")
4、flag包命令列引數解析
固定用法,定義好引數後,通過呼叫 flag.Parse()
來對命令列引數進行解析寫入註冊的 flag
裡,進而解析獲取引數值,通過檢視原始碼中也是呼叫的 os.Args
原始碼路徑 go/src/flag/flag.go
// Parse parses the command-line flags from os.Args[1:]. Must be called // after all flags are defined and before flags are accessed by the program. func Parse() { // Ignore errors; CommandLine is set for ExitOnError. CommandLine.Parse(os.Args[1:]) }
進而檢視 Parse
方法的原始碼
func (f *FlagSet) Parse(arguments []string) error { f.parsed = true f.args = arguments for { seen, err := f.parseOne() if seen { continue } if err == nil { break } switch f.errorHandling { case ContinueOnError: return err case ExitOnError: if err == ErrHelp { os.Exit(0) } os.Exit(2) case PanicOnError: panic(err) } } return nil }
真正解析引數的是 parseOne
方法(這裡省略原始碼),結論是
- 當遇到單獨的一個 "-" 或不是 "-" 開始時,會停止解析
- 遇到連續的兩個 "-" 時,解析停止
- 在終止符"-"之後停止
解析引數時,對於引數的指定方式一般有"-"、"--"、以及是否空格等方式,組合下來有如下幾種方式
-flag xxx | 空格和一個 - 符號 |
---|---|
--flag xxx | 空格和兩個 - 符號 |
-flag=xxx | 等號和一個 - 符號 |
--flag=xxx | 等號和兩個 - 符號 |
其中, -flag xxx
方式最為常用,如果引數是布林型,只能用等號方式指定
5、flag包命令列幫助
flag
包預設會根據定義的命令列引數,在使用時如果不輸入引數就列印對應的幫助資訊
這樣的幫助資訊我們可以對其進行覆蓋去改變預設的 Usage
package main import ( "flag" "fmt" ) func main() { var host string var port int var verbor bool var help bool // 繫結命令列引數與變數關係 flag.StringVar(&host, "H", "127.0.0.1", "ssh host") flag.IntVar(&port, "P", 22, "ssh port") flag.BoolVar(&verbor, "v", false, "detail log") flag.BoolVar(&help, "h", false, "help") // 自定義-h flag.Usage = func() { fmt.Println(` Usage: flag [-H addr] [-p port] [-v] Options: `) flag.PrintDefaults() } // 解析命令列引數 flag.Parse() if help { flag.Usage() } else { fmt.Println(host, port, verbor) } } /* ➜ go run flag_args.go -h Usage: flag [-H addr] [-p port] [-v] Options: -H string ssh host (default "127.0.0.1") -P int ssh port (default 22) -h help -v detail log */
6、flag定義短引數和長引數
簡單來說,短引數和長引數,就是例如我們在使用某些命令時,檢視命令版本可以輸入 -V
,也可以輸入 --version
。這種情況下, flag
並沒有預設支援,但是可以通過可以兩個選項共享同一個變數來實現,即通過給某個相同的變數設定不同的選項,引數在初始化的時候其順序是不固定的,因此還需要保證其擁有相同的預設值
package main import ( "fmt" "flag" ) var logLevel string func init() { const ( defaultLogLevel = "DEBUG" usage = "set log level" ) flag.StringVar(&logLevel, "log_level", defaultLogLevel, usage) flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(shorthand)") } func main() { flag.Parse() fmt.Println("log level:", logLevel) }
通過 const
宣告公共的常量,並在預設值以及幫助資訊中去使用,這樣就可以實現了
7、示例
實現計算字串或目錄下遞迴計算檔案 md5
的命令,類似 linux
的 md5sum
命令
其中利用 bufio
分批次讀取檔案,防止檔案過大時造成資源佔用高
package main import ( "bufio" "crypto/md5" "flag" "fmt" "io" "os" "strings" ) func md5reader(reader *bufio.Reader) string { // hasher := md5.New() // 定義MD5 hash計算器 bytes := make([]byte, 1024*1024*10) // 分批次讀取檔案 for { n, err := reader.Read(bytes) if err != nil { if err != io.EOF { return "" } break } else { hasher.Write(bytes[:n]) } } return fmt.Sprintf("%x", hasher.Sum(nil)) } func md5file(path string) (string, error) { file, err := os.Open(path) if err != nil { return "", err } else { defer file.Close() return md5reader(bufio.NewReader(file)), nil } } func md5str(txt string) (string, error) { return md5reader(bufio.NewReader(strings.NewReader(txt))), nil //return fmt.Sprintf("%x", md5.Sum([]byte(txt))) } func main() { txt := flag.String("s", "", "md5 txt") path := flag.String("f", "", "file path") help := flag.Bool("h", false, "help") flag.Usage = func() { fmt.Println(` Usage: md5 [-s 123abc] [-f path] Options: `) flag.PrintDefaults() } flag.Parse() if *help || *txt == "" && *path == "" { flag.Usage() } else { var md5 string var err error if *path != "" { md5, err = md5file(*path) } else { md5, err = md5str(*txt) } if err != nil { fmt.Println(err) } else { fmt.Println(md5) } } }
編譯生成二進位制檔案
➜ go build -o md5go -x md5_bufio.go ➜ ll md5go -rwxr-xr-x 1 ssgeek staff 1.9M Oct 2 00:54 md5go
測試使用
➜ ./md5go -h Usage: md5 [-s 123abc] [-f path] Options: -f string file path -h help -s string md5 txt ➜ ./md5go -s 123456 e10adc3949ba59abbe56e057f20f883e ➜ ./md5go -f md5_bufio.go 8607a07cbb98cec0e9abe14b0db0bee6
Just here,see you ~
- Gradle打包工具入門
- 服務網格和Istio初識-續
- 服務網格和Istio初識
- Golang與非對稱加密
- ack叢集Terway網路場景下的vSwitch擴容
- Golang與對稱加密
- 基於ack k8s叢集排程的方案設計
- 基於Dockerfile構建容器映象的最佳實踐
- Golang反射-下篇
- Golang反射-上篇
- Azure DevOps的使用入門
- Golang介面型別-下篇
- Golang介面型別-上篇
- 基於Python實現原生的登入驗證碼
- Golang開發命令列工具之flag包的使用
- Golang檔案操作-下篇
- k8s環境下處理容器時間問題的多種姿勢
- Golang基準測試
- 淺談Prometheus的資料儲存
- Golang單元測試