深入瞭解Go flag

一、前言

flag主要用以對命令行工具的參數的解析獲取,其源碼位於/flag/flag.go中。

二、flag使用

flag一般使用方法:

(1)聲明變量並綁定

有兩種方式:

①Var形式

var (
    _para1 string
    _para2 int
    _para3 bool
    ...
)

func init{
    flag.StringVar(&_para1, "para1", "", "para1")
    flag.IntVar(&_para2, "para2", 0, "para2")
    flag.IntVar(&_para3, "para3", false, "para3")
    ...
}

②不帶Var格式

var (
_para1 *string
_para2 *int
_para3 *bool

)

func init(
    _para1=flag.String( "para1", "", "para1")
    _para2=flag.Int("para2", 0, "para2")
    _para3=flag.Bool("para3",false,"para3")
    ...
)

兩者的功能是一致的,具體原因後面說。

(2)main中Parse

func main(){
    flag.Parse()
    ...
}

調用flag.Parse()後參數的值會解析到綁定的變量中。

(3)使用

./test -para1 value1 -para2 1 -para3 true

三、flag源碼

1.綁定

綁定有兩種形式,我們以string的處理做說明,其他格式的,可參考具體源碼。

// StringVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a string variable in which to store the value of the flag.
func (f *FlagSet) StringVar(p *string, name string, value string, usage string) {
    f.Var(newStringValue(value, p), name, usage)
}

// StringVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a string variable in which to store the value of the flag.
func StringVar(p *string, name string, value string, usage string) {
    CommandLine.Var(newStringValue(value, p), name, usage)
}

// String defines a string flag with specified name, default value, and usage string.
// The return value is the address of a string variable that stores the value of the flag.
func (f *FlagSet) String(name string, value string, usage string) *string {
    p := new(string)
    f.StringVar(p, name, value, usage)
    return p
}

// String defines a string flag with specified name, default value, and usage string.
// The return value is the address of a string variable that stores the value of the flag.
func String(name string, value string, usage string) *string {
    return CommandLine.String(name, value, usage)
}

我們可以看到,兩者調用的均爲Var func,其他類型的參數最終也是調用Var來處理。不同之處是,String會new一個string的指針,然後用聲明的變量指針指向這個指針的值,而StringVar是直接使用我們傳入的指針。

newStringValue主要是完成參數的初始化設置,即提供的默認值設置,並進行相應的類型轉換。

func (f *FlagSet) Var(value Value, name string, usage string) {
    // Remember the default value as a string; it won't change.
    flag := &Flag{name, usage, value, value.String()}
    _, alreadythere := f.formal[name]
    if alreadythere {//若已存在則panic報錯
        var msg string
        if f.name == "" {
            msg = fmt.Sprintf("flag redefined: %s", name)
        } else {
            msg = fmt.Sprintf("%s flag redefined: %s", f.name, name)
        }
        fmt.Fprintln(f.Output(), msg)
        panic(msg) // Happens only if flags are declared with identical names
    }
    if f.formal == nil {
        f.formal = make(map[string]*Flag)
    }
    f.formal[name] = flag
}

Var中完成flag的封裝,以name爲key存入format(map)中。若name已存在,說明重複定義。

2.解析

// Parse parses flag definitions from the argument list, which should not
// include the command name. Must be called after all flags in the FlagSet
// are defined and before flags are accessed by the program.
// The return value will be ErrHelp if -help or -h were set but not defined.
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:
            os.Exit(2)
        case PanicOnError:
            panic(err)
        }
    }
    return nil
}

解析是一個接一個參數完成的,具體由parseOne實現。解析完一個flag才繼續下一個,中間發生任何錯誤,不再繼續進行,報錯退出。

parseOne

// parseOne parses one flag. It reports whether a flag was seen.
func (f *FlagSet) parseOne() (bool, error) {
    if len(f.args) == 0 {//不能爲空
        return false, nil
    }
    s := f.args[0]
    if len(s) < 2 || s[0] != '-' {//key必須包含’-',且長度必須大於1
        return false, nil
    }
    numMinuses := 1
    if s[1] == '-' {
        numMinuses++
        if len(s) == 2 { // "--" terminates the flags
            f.args = f.args[1:]
            return false, nil
        }
    }
    name := s[numMinuses:]
    if len(name) == 0 || name[0] == '-' || name[0] == '=' {
        return false, f.failf("bad flag syntax: %s", s)
    }

    // it's a flag. does it have an argument?
    f.args = f.args[1:]
    hasValue := false
    value := ""
    for i := 1; i < len(name); i++ { // equals cannot be first
        if name[i] == '=' {   //處理中包含'='的問題,以'='爲界限拆分爲key、value兩部分
            value = name[i+1:]
            hasValue = true
            name = name[0:i]
            break
        }
    }
    m := f.formal
    flag, alreadythere := m[name] // BUG
    if !alreadythere {//解析到不存在的參數
        if name == "help" || name == "h" { // special case for nice help message.
            f.usage()
            return false, ErrHelp
        }
        return false, f.failf("flag provided but not defined: -%s", name)
    }

    if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg
    //bool相關問題,提供value,則設置value,不提供value,則設置爲true
        if hasValue {
            if err := fv.Set(value); err != nil {
                return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err)
            }
        } else {
            if err := fv.Set("true"); err != nil {
                return false, f.failf("invalid boolean flag %s: %v", name, err)
            }
        }
    } else {
        // It must have a value, which might be the next argument.
        if !hasValue && len(f.args) > 0 {
            // value is the next arg
            //直接取下一個值爲key對應的值
            hasValue = true
            value, f.args = f.args[0], f.args[1:]
        }
        if !hasValue {
            return false, f.failf("flag needs an argument: -%s", name)
        }
        //設置值
        if err := flag.Value.Set(value); err != nil {
            return false, f.failf("invalid value %q for flag -%s: %v", value, name, err)
        }
    }
    if f.actual == nil {
        f.actual = make(map[string]*Flag)
    }
    // 存儲
    f.actual[name] = flag
    return true, nil
}

解析就是找到key及對應的value。key與value總是成對出現的,要想找到value,首要就是先找到key,flag要求key的傳入方式是-key,代碼就是從找-符號開始。

大致過程如下:

1.尋找key

  • key、value是成對出現的,所以要求len(s)>2
  • key必須以-開頭,但不能以--開頭
  • 獲取-後的部分爲name,注意name中可能會包含value

2.處理name中包含value的情況(-key=value

  • 以第一個=爲分割符,分成兩部分,前一部分爲key,後一部分爲value

3.確認解析的key對應的flag是否存在,沒有則報錯

4.對於bool類型的flag,可以不提供value,有value的話用value,沒有的話取默認值

5.其他類型的flag必須提供value,當前name中沒有value,則取參數中的下一個值

6.設置flag獲取到value

7.將flag存入map中

3.更多發現

從以上源碼中我們發現了:

(1)參數以"="傳值的形式

./test -para1=value1 -para2=1 -para3=true

(2)value中包含"="是允許的

-para1 value1=2//①
-para1=value1=2//②

①是因爲代碼不會檢查value的

②因爲代碼只要處理了第一個"=",就不再處理了,代碼如下:。

for i := 1; i < len(name); i++ { // equals cannot be first
        if name[i] == '=' {   //處理中包含'='的問題,以'='爲界限拆分爲key、value兩部分
            value = name[i+1:]
            hasValue = true
            name = name[0:i]
            break
        }
}

(3)"="傳值,則"="右側不能爲空

但是左側可以爲空,雖然沒什麼實際使用價值。

(4)bool類型可以不傳value

對於bool類型的參數,可以不傳value,不過會默認設置爲true。

(5)傳值方式可以混用

實際使用時,兩種形式是可以混合使用的,爲避免理解錯誤,非常不建議混用。

./test -para1 value1 -para2=value2 -para3

公衆號

鄙人剛剛開通了公衆號,專注於分享Go開發相關內容,望大家感興趣的支持一下,在此特別感謝。

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