評論有人提到沒有例子,不知道講的是什麼。所以,爲了你們可以更好地理解,特地加了一個示例。其實本文更多講解的是 flag 的實現原理,加上示例以後,就更好地知道怎麼使用了。建議閱讀 《Go語言標準庫》一書的對應章節:flag – 命令行參數解析。php
在寫命令行程序(工具、server)時,對命令參數進行解析是常見的需求。各類語言通常都會提供解析命令行參數的方法或庫,以方便程序員使用。若是命令行參數純粹本身寫代碼解析,對於比較複雜的,仍是挺費勁的。在go標準庫中提供了一個包:flag,方便進行命令行解析。nginx
注:區分幾個概念
1)命令行參數(或參數):是指運行程序提供的參數
2)已定義命令行參數:是指程序中經過flag.Xxx等這種形式定義了的參數
3)非flag(non-flag)命令行參數(或保留的命令行參數):後文解釋git
1、使用示例
咱們以 nginx 爲例,執行 nginx -h,輸出以下:程序員
nginx version: nginx/1.10.0
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-T : test configuration, dump it and exit
-q : suppress non-error messages during configuration testing
-s signal : send signal to a master process: stop, quit, reopen, reload
-p prefix : set prefix path (default: /usr/local/nginx/)
-c filename : set configuration file (default: conf/nginx.conf)
-g directives : set global directives out of configuration file
咱們經過 `flag` 實現相似 nginx 的這個輸出,建立文件 nginx.go,內容以下:github
package main
import (編程
"flag"數據結構
"fmt"app
"os"編程語言
)函數
// 實際中應該用更好的變量名
var (
h bool
v, V bool
t, T bool
q *bool
s string
p string
c string
g string
)
func init() {
flag.BoolVar(&h, "h", false, "this help")
flag.BoolVar(&v, "v", false, "show version and exit")
flag.BoolVar(&V, "V", false, "show version and configure options then exit")
flag.BoolVar(&t, "t", false, "test configuration and exit")
flag.BoolVar(&T, "T", false, "test configuration, dump it and exit")
// 另外一種綁定方式
q = flag.Bool("q", false, "suppress non-error messages during configuration testing")
// 注意 `signal`。默認是 -s string,有了 `signal` 以後,變爲 -s signal
flag.StringVar(&s, "s", "", "send `signal` to a master process: stop, quit, reopen, reload")
flag.StringVar(&p, "p", "/usr/local/nginx/", "set `prefix` path")
flag.StringVar(&c, "c", "conf/nginx.conf", "set configuration `file`")
flag.StringVar(&g, "g", "conf/nginx.conf", "set global `directives` out of configuration file")
// 改變默認的 Usage
flag.Usage = usage
}
func main() {
flag.Parse()
if h {
flag.Usage()
}
}
func usage() {
fmt.Fprintf(os.Stderr, `nginx version: nginx/1.10.0
Usage: nginx [-hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
`)
flag.PrintDefaults()
}
執行:go run nginx.go -h,(或 go build -o nginx && ./nginx -h)輸出以下:
nginx version: nginx/1.10.0
Usage: nginx [-hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-T test configuration, dump it and exit
-V show version and configure options then exit
-c file
set configuration file (default "conf/nginx.conf")
-g directives
set global directives out of configuration file (default "conf/nginx.conf")
-h this help
-p prefix
set prefix path (default "/usr/local/nginx/")
-q suppress non-error messages during configuration testing
-s signal
send signal to a master process: stop, quit, reopen, reload
-t test configuration and exit
-v show version and exit
仔細理解以上例子,若是有不理解的,看完下文的講解再回過頭來看。
2、flag包概述
flag包實現了命令行參數的解析。
定義flags有兩種方式:
1)flag.Xxx(),其中Xxx能夠是Int、String等;返回一個相應類型的指針,如:
var ip = flag.Int("flagname", 1234, "help message for flagname")
2)flag.XxxVar(),將flag綁定到一個變量上,如:
var flagvar int
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
另外,還能夠建立自定義flag,只要實現flag.Value接口便可(要求receiver是指針),這時候能夠經過以下方式定義該flag:
flag.Var(&flagVal, "name", "help message for flagname")
例如,解析我喜歡的編程語言,咱們但願直接解析到 slice 中,咱們能夠定義以下 Value:
type sliceValue []string
func newSliceValue(vals []string, p *[]string) *sliceValue {
*p = vals
return (*sliceValue)(p)
}
func (s *sliceValue) Set(val string) error {
*s = sliceValue(strings.Split(val, ","))
return nil
}
func (s *sliceValue) Get() interface{} { return []string(*s) }
func (s *sliceValue) String() string { return strings.Join([]string(*s), ",") }
以後能夠這麼使用:
var languages []string
flag.Var(newSliceValue([]string{}, &languages), "slice", "I like programming `languages`")
這樣經過 -slice 「go,php」 這樣的形式傳遞參數,languages 獲得的就是 [go, php]。
flag 中對 Duration 這種非基本類型的支持,使用的就是相似這樣的方式。
在全部的flag定義完成以後,能夠經過調用flag.Parse()進行解析。
命令行flag的語法有以下三種形式:
-flag // 只支持bool類型
-flag=x
-flag x // 只支持非bool類型
其中第三種形式只能用於非bool類型的flag,緣由是:若是支持,那麼對於這樣的命令 cmd -x *,若是有一個文件名字是:0或false等,則命令的原意會改變(之因此這樣,是由於bool類型支持-flag這種形式,若是bool類型不支持-flag這種形式,則bool類型能夠和其餘類型同樣處理。也正由於這樣,Parse()中,對bool類型進行了特殊處理)。默認的,提供了-flag,則對應的值爲true,不然爲flag.Bool/BoolVar中指定的默認值;若是但願顯示設置爲false則使用-flag=false。
int類型能夠是十進制、十六進制、八進制甚至是負數;bool類型能夠是1, 0, t, f, true, false, TRUE, FALSE, True, False。Duration能夠接受任何time.ParseDuration能解析的類型
3、類型和函數
在看類型和函數以前,先看一下變量
ErrHelp:該錯誤類型用於當命令行指定了-help參數但沒有定義時。
Usage:這是一個函數,用於輸出全部定義了的命令行參數和幫助信息(usage message)。通常,當命令行參數解析出錯時,該函數會被調用。咱們能夠指定本身的Usage函數,即:flag.Usage = func(){}
一、函數
go標準庫中,常常這麼作:
定義了一個類型,提供了不少方法;爲了方便使用,會實例化一個該類型的實例(通用),這樣即可以直接使用該實例調用方法。好比:encoding/base64中提供了StdEncoding和URLEncoding實例,使用時:base64.StdEncoding.Encode()
在flag包中,進行了進一步封裝:將FlagSet的方法都從新定義了一遍,也就是提供了一序列函數,而函數中只是簡單的調用已經實例化好了的FlagSet實例:commandLine 的方法,這樣commandLine實例便不須要export。這樣,使用者是這麼調用:flag.Parse()而不是flag.commandLine.Parse()。(Go 1.2 版本起改成了 CommandLine)
這裏不詳細介紹各個函數,在類型方法中介紹。
二、類型(數據結構)
1)ErrorHandling
type ErrorHandling int
該類型定義了在參數解析出錯時錯誤處理方式定義了。三個該類型的常量:
const (
ContinueOnError ErrorHandling = iota
ExitOnError
PanicOnError
)
三個常量在源碼的 FlagSet 的方法 parseOne() 中使用了。
2)Flag
// A Flag represents the state of a flag.
type Flag struct {
Name string // name as it appears on command line
Usage string // help message
Value Value // value as set
DefValue string // default value (as text); for usage message
}
Flag類型表明一個flag的狀態。
好比:autogo -f abc.txt,代碼 flag.String(「f」, 「a.txt」, 「usage」),則該Flag實例(能夠經過flag.Lookup(「f」)得到)相應的值爲:f, usage, abc.txt, a.txt。
3)FlagSet
// A FlagSet represents a set of defined flags.
type FlagSet struct {
// Usage is the function called when an error occurs while parsing flags.
// The field is a function (not a method) that may be changed to point to
// a custom error handler.
Usage func()
name string // FlagSet的名字。CommandLine 給的是 os.Args[0]
parsed bool // 是否執行過Parse()
actual map[string]*Flag // 存放實際傳遞了的參數(即命令行參數)
formal map[string]*Flag // 存放全部已定義命令行參數
args []string // arguments after flags // 開始存放全部參數,最後保留 非flag(non-flag)參數
exitOnError bool // does the program exit if there's an error?
errorHandling ErrorHandling // 當解析出錯時,處理錯誤的方式
output io.Writer // nil means stderr; use out() accessor
}
4)Value接口
// Value is the interface to the dynamic value stored in a flag.
// (The default value is represented as a string.)
type Value interface {
String() string
Set(string) error
}
全部參數類型須要實現Value接口,flag包中,爲int、float、bool等實現了該接口。藉助該接口,咱們能夠自定義flag
4、主要類型的方法(包括類型實例化)
flag包中主要是FlagSet類型。
一、實例化方式
NewFlagSet()用於實例化FlagSet。預約義的FlagSet實例 CommandLine 的定義方式:
// The default set of command-line flags, parsed from os.Args.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
可見,默認的FlagSet實例在解析出錯時會退出程序。
因爲FlagSet中的字段沒有export,其餘方式得到FlagSet實例後,好比:FlagSet{}或new(FlagSet),應該調用Init()方法,初始化name和errorHandling。
二、定義flag參數方法
這一序列的方法都有兩種形式,在一開始已經說了兩種方式的區別。這些方法用於定義某一類型的flag參數。
三、解析參數(Parse)
func (f *FlagSet) Parse(arguments []string) error
從參數列表中解析定義的flag。參數arguments不包括命令名,即應該是os.Args[1:]。
事實上,flag.Parse()函數就是這麼作的:
// 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:])
}
該方法應該在flag參數定義後而具體參數值被訪問前調用。
若是提供了-help參數(命令中給了)但沒有定義(代碼中),該方法返回ErrHelp錯誤。默認的CommandLine,在Parse出錯時會退出(ExitOnError)
爲了更深刻的理解,咱們看一下Parse(arguments []string)的源碼:
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。
結合parseOne方法,咱們來解釋non-flag以及包文檔中的這句話:
Flag parsing stops just before the first non-flag argument (「-」 is a non-flag argument) or after the terminator 「–「.
咱們須要瞭解解析何時中止
根據Parse()中for循環終止的條件(不考慮解析出錯),咱們知道,當parseOne返回false, nil時,Parse解析終止。正常解析完成咱們不考慮。看一下parseOne的源碼發現,有兩處會返回false, nil。
1)第一個non-flag參數
s := f.args[0]
if len(s) == 0 || s[0] != '-' || len(s) == 1 {
return false, nil
}
也就是,當遇到單獨的一個」-「或不是」-「開始時,會中止解析。好比:
./autogo – -f或./autogo build -f
這兩種狀況,-f都不會被正確解析。像該例子中的」-「或build(以及以後的參數),咱們稱之爲non-flag參數
2)兩個連續的」–「
if s[1] == '-' {
num_minuses++
if len(s) == 2 { // "--" terminates the flags
f.args = f.args[1:]
return false, nil
}
}
也就是,當遇到連續的兩個」-「時,解析中止。
說明:這裏說的」-「和」–「,位置和」-f」這種的同樣。也就是說,下面這種狀況並非這裏說的:
./autogo -f —
這裏的」–「會被當成是f的值
parseOne方法中接下來是處理-flag=x,而後是-flag(bool類型)(這裏對bool進行了特殊處理),接着是-flag x這種形式,最後,將解析成功的Flag實例存入FlagSet的actual map中。
另外,在parseOne中有這麼一句:
1 f.args = f.args[1:]
也就是說,每執行成功一次parseOne,f.args會少一個。因此,FlagSet中的args最後留下來的就是全部non-flag參數。
四、Arg(i int)和Args()、NArg()、NFlag()
Arg(i int)和Args()這兩個方法就是獲取non-flag參數的;NArg()得到non-flag個數;NFlag()得到FlagSet中actual長度(即被設置了的參數個數)。
五、Visit/VisitAll
這兩個函數分別用於訪問FlatSet的actual和formal中的Flag,而具體的訪問方式由調用者決定。
六、PrintDefaults()
打印全部已定義參數的默認值(調用VisitAll),默認輸出到標準錯誤,除非指定了FlagSet的output(經過SetOutput()設置)
七、Set(name, value string)
設置某個flag的值(經過Flag的name)
5、總結
使用建議:雖然上面講了那麼多,通常來講,咱們只簡單的定義flag,而後parse,就如同開始的例子同樣。
若是項目須要複雜或更高級的命令行解析方式,能夠試試goptions包
若是想要像go工具那樣的多命令(子命令)處理方式,能夠試試command
更新:
2017-10-14:複雜的命令解析,推薦 https://github.com/urfave/cli 或者 https://github.com/spf13/cobra