golang 標準命令行解析庫 flag

flag 庫實現了對命令行參數的解析

基本用法

package main

import (
    "fmt"
    "flag"
)

func main() {
    b := flag.Bool("b", false, "bool flag")
	s := flag.String("s", "hello golang", "string flag")
	flag.Parse()
    fmt.Println("b is", *b)
    fmt.Println("s is", *s)
}

上面代碼指定了兩個選項:

  • bool 類型的 b 選項,默認值爲 false,幫助信息 bool flag
  • string 類型的 s 選項,默認值爲 hello golang,幫助信息 string flag

執行 go run main.go 將輸出 b 和 s 的值

b is false
s is hello golang

執行 go run main.go -b -s "hello world" 將修改 b 和 s 的值

b is true
s is hello world

執行 go run main.go -h 可以打印幫助信息

Usage of main:
  -b	bool flag
  -s string
    	string flag (default "hello golang")

命令行語法

-b -i 100 -f=12.12 --s golang --d=20s
  • - 或者 -- 開頭指定選項名,--- 是等效的
  • bool 選項後面需要接一個值賦值 -flag value-flag=value--falg value--flag=value
  • bool 選項後面可以不賦值 -flag-flag=false--flag--flag=1
  • 解析過程中遇到非選項字段,將立即結束解析,後面的字段會被放入到 args 變量中
  • 所有的選項必須是已定義的,遇到未知的選項將返回錯誤

主要接口

CommandLine

var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

FlagSet 是 flag 的核心解析類,爲了方便使用,flag 內部提供了一個 FlagSet 對象 CommandLine,並且爲 CommandLine 的所有方法封裝了一下直接對外

例如: flag.Int("i", 10, "int flag") 其實是 CommandLine.Int("i", 10, "int flag")

func Int(name string, value int, usage string) *int {
	return CommandLine.Int(name, value, usage)
}

添加選項

flagSet := flag.NewFlagSet("test flag", flag.ExitOnError)

i := flagSet.Int("i", 10, "int flag")
f := flagSet.Float64("f", 11.11, "float flag")
s := flagSet.String("s", "hello world", "string flag")
d := flagSet.Duration("d", time.Duration(30)*time.Second, "string flag")
b := flagSet.Bool("b", false, "bool flag")

So(*i, ShouldEqual, 10)
So(*f, ShouldAlmostEqual, 11.11)
So(*s, ShouldEqual, "hello world")
So(*d, ShouldEqual, time.Duration(30)*time.Second)
So(*b, ShouldBeFalse)

flag 提供了兩種方式添加選項

func Int(name string, value int, usage string) *int
func IntVar(p *int, name string, value int, usage string)
  • type 返回一個對應類型的地址,調用 Parse 之後將會被重新複製
  • typeVar 沒有返回值,傳入一個地址,Parse 之後的值寫入到傳入的地址

解析

正常的解析過程

err := flagSet.Parse(strings.Split("-b -i 100 -f=12.12 --s golang --d=20s", " "))
So(err, ShouldBeNil)
So(*i, ShouldEqual, 100)
So(*f, ShouldAlmostEqual, 12.12)
So(*s, ShouldEqual, "golang")
So(*d, ShouldEqual, time.Duration(20)*time.Second)
So(*b, ShouldBeTrue)

解析過程中遇到非選項參數

// -i 後面期望之後一個參數,但是提供了兩個,解析會立即停止,剩下的參數會寫入到 args 中
err := flagSet.Parse(strings.Split("-b -i 100 101 -f 12.12 -s golang -d 20s", " "))
So(err, ShouldBeNil)
So(*b, ShouldBeTrue)
So(*i, ShouldEqual, 100)
So(*f, ShouldEqual, 11.11)          // not override
So(*s, ShouldEqual, "hello world")  // not override
So(*d, ShouldEqual, 30*time.Second) // not override

遇到未知的選項將出錯,下面代碼將直接返回錯誤

flag 提供三種錯誤處理的方式:

  • ContinueOnError: 通過 Parse 的返回值返回錯誤
  • ExitOnError: 調用 os.Exit(2) 直接退出程序,這是默認的處理方式
  • PanicOnError: 調用 panic 拋出錯誤
flagSet.Parse(strings.Split("-xx abc", " "))

解析狀態

err := flagSet.Parse(strings.Split("-b -i 100 101 -f 12.12 -s golang -d 20s", " "))
So(flagSet.NFlag(), ShouldEqual, 2)
So(flagSet.NArg(), ShouldEqual, 7)
So(flagSet.Args(), ShouldResemble, []string{
    "101", "-f", "12.12", "-s", "golang", "-d", "20s",
})
  • NFlag: 解析了多少個選項
  • NArg: 剩下多少個參數沒有被解析
  • Args: 返回剩下的參數

設置單個選項

err := flagSet.Set("i", "120")
So(err, ShouldBeNil)
So(*i, ShouldEqual, 120)

遍歷和查詢選項

// 遍歷所有設置過得選項
flagSet.Visit(func(f *flag.Flag) {
    fmt.Println(f.Name)
})

// 遍歷所有選項
flagSet.VisitAll(func(f *flag.Flag) {
    fmt.Println(f.Name)
})

f := flagSet.Lookup("i")
So(f.Name, ShouldEqual, "i")
So(f.DefValue, ShouldEqual, "10")
So(f.Usage, ShouldEqual, "int flag")
So(f.Value.String(), ShouldEqual, "10")

設計思路

解析過程

func (f *FlagSet)Parse([]string) error
  1. 遍歷字符串數字
  2. 檢查當前的字符串,如果以 - 或者 -- 開頭說明是選項,否則解析就結束了
  3. 檢查當前字符串中是否有 =,如果有 =,直接設置選項的值爲等號後面的內容
  4. 如果沒有 =,用下一個字符串作爲當前選項的值

value 的設計

如果我們的設計是在解析之後,用戶顯示調用類似 GetInt 之類的方法,再返回一個值,這樣就很容易實現了,但是 Value 的設計難度在於在解析之前需要預先爲每一個選項返回一個地址,而選項的類型也不是固定的

type Value interface {
	String() string
	Set(string) error
}

Value 被設計成了一個接口,爲不同的數據類型實現這個接口,返回給用戶的地址就是這個接口的實例數據,解析過程中,可以通過 Set 方法修改它的值,這個設計確實還挺巧妙的

鏈接

轉載請註明出處
本文鏈接:https://tech.hatlonely.com/article/64

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章