關(guān)于Golang標準庫flag的全面講解
前言:
今天來聊聊Go語言標準庫中一個非常簡單的庫flag,這個庫的代碼量只有1000行左右,卻提供了非常完善的命令行參數(shù)解析功能。
命令行參數(shù)
如果你有使用過類Unix(比如MacOS,Linux)等操作系統(tǒng),相信你應(yīng)該明白命令參數(shù)是什么,比如下面的兩條命令:
$ mysql -u root -p 123456 $ ls -al
第一條命令是MySQL的客戶端,其-u root
和-p 123456
就是命令行參數(shù),第二條命令用于顯示當前目錄的文件及目錄,該命令中-al
就是命令行參數(shù)。
flag庫的作用就是幫我們將命令后面的選項參數(shù)解析到對應(yīng)的變量中。
使用詳解
要了解一個庫,須從使用開始,下面我們通過一個簡單的示例來快速了解flag庫的使用,這個示例可以接收從命令行傳遞的用于連接數(shù)據(jù)庫的參數(shù),
代碼如下:
package main import ( "flag" "fmt" ) var ( host string dbName string port int user string password string ) func main() { flag.StringVar(&host, "host", "", "數(shù)據(jù)庫地址") flag.StringVar(&dbName, "db_name", "", "數(shù)據(jù)庫名稱") flag.StringVar(&user, "user", "", "數(shù)據(jù)庫用戶") flag.StringVar(&password, "password", "", "數(shù)據(jù)庫密碼") flag.IntVar(&port, "port", 3306, "數(shù)據(jù)庫端口") flag.Parse() fmt.Printf("數(shù)據(jù)庫地址:%s\n", host) fmt.Printf("數(shù)據(jù)庫名稱:%s\n", dbName) fmt.Printf("數(shù)據(jù)庫用戶:%s\n", user) fmt.Printf("數(shù)據(jù)庫密碼:%s\n", password) fmt.Printf("數(shù)據(jù)庫端口:%d\n", port) }
在命令行窗口輸入以下命令,開始運行程序
go run main.go -host=localhost -user=test -password=123456 -db_name=test -port=3306
運行結(jié)束,輸出結(jié)果如下所示:
數(shù)據(jù)庫地址:localhost
數(shù)據(jù)庫名稱:test
數(shù)據(jù)庫用戶:test
數(shù)據(jù)庫密碼:123456
數(shù)據(jù)庫端口:3306
上面的示例就是一個解析命令行選項參數(shù)的模板,包括下面三個步驟:
- 定義好接收參數(shù)的變量。
- 調(diào)用flag.StringVar()等函數(shù)將命令行選項與變量綁定。
- 調(diào)用flag.Parse()函數(shù),開始解析變量。
在上面程序中,我們用了StringVar函數(shù)綁定字符串類型的參數(shù),用了IntVar函數(shù)綁定整數(shù)類型的參數(shù),除了字符串和整型,flag支持boolean,Duration,float64,Int64,uint,uint64等類型,下面是這些函數(shù)的定義,用法與StringVar相同。
func BoolVar(p *bool, name string, value bool, usage string) func DurationVar(p *time.Duration, name string, value time.Duration, usage string) func Float64Var(p *float64, name string, value float64, usage string) func Int64Var(p *int64, name string, value int64, usage string) func IntVar(p *int, name string, value int, usage string) func StringVar(p *string, name string, value string, usage string) func Uint64Var(p *uint64, name string, value uint64, usage string) func UintVar(p *uint, name string, value uint, usage string)
上面列出的函數(shù)帶有Var
后綴,表示需要我們自己傳遞一個變量去接收命令行參數(shù),而flag在這些函數(shù)有基礎(chǔ)上,封裝了下面列表的函數(shù),這些函數(shù)沒有Var
后綴,跟上面的函數(shù)相比少了一個參數(shù),卻多了一個返回值,這個返回值就是接收命令參數(shù)的變量指針。
func Bool(name string, value bool, usage string) *bool func Duration(name string, value time.Duration, usage string) *time.Duration func Float64(name string, value float64, usage string) *float64 func Int(name string, value int, usage string) *int func Int64(name string, value int64, usage string) *int64 func String(name string, value string, usage string) *string func Uint(name string, value uint, usage string) *uint func Uint64(name string, value uint64, usage string) *uint64
所以我們把上面的示例改寫為以下的樣子:
package main import ( "flag" "fmt" ) func main() { host := flag.String("host", "", "數(shù)據(jù)庫地址") dbName := flag.String("db_name", "", "數(shù)據(jù)庫名稱") user := flag.String("user", "", "數(shù)據(jù)庫用戶") password := flag.String("password", "", "數(shù)據(jù)庫密碼") port := flag.Int("port", 3306, "數(shù)據(jù)庫端口") flag.Parse() fmt.Printf("數(shù)據(jù)庫地址:%s\n", *host) fmt.Printf("數(shù)據(jù)庫名稱:%s\n", *dbName) fmt.Printf("數(shù)據(jù)庫用戶:%s\n", *user) fmt.Printf("數(shù)據(jù)庫密碼:%s\n", *password) fmt.Printf("數(shù)據(jù)庫端口:%d\n", *port) }
另外,運行程序時,在后面跟上-h
或--help
來查看命令的參數(shù)選項,如:
go run main.go --help Usage of main: -db_name string 數(shù)據(jù)庫名稱 -host string 數(shù)據(jù)庫地址 -password string 數(shù)據(jù)庫密碼 -port int 數(shù)據(jù)庫端口 (default 3306) -user string 數(shù)據(jù)庫用戶
選項語法
flag支持以下三種命令行格式,參數(shù)前面的-
也可以換成--
,在flag庫中,--
并不是表示長選項的意思。
cmd -flag cmd -flag=x cmd -flag x
- 第一種只用布爾值的選項,如果該參數(shù)出現(xiàn),則為true,不出則為默認值,而其他數(shù)據(jù)類型不能使用這種格式傳值。
- 第二種可適用任何類型,因此也是最常用的格式。
- 第三種不可用于布爾值的選項。
flag在解析參數(shù)時,如果遇到第一個非選項參數(shù)(不是以-或--開頭的)或終止符--,就會停止解析,
比如上面的示例中,我們將運行命令改成下面的樣子:
go run main.go -host=localhost noflag -user=test -password=123456 -db_name=test -port=3306
運行結(jié)果如下,可以看到解析-host參數(shù)之后遇到了noflag這樣的非選項參數(shù),flag就停止解析了,所以后面的參數(shù)都只輸出了默認值。
數(shù)據(jù)庫地址:localhost
數(shù)據(jù)庫名稱:
數(shù)據(jù)庫用戶:
數(shù)據(jù)庫密碼:
數(shù)據(jù)庫端口:3306
整數(shù)類型的參數(shù)可以接收十進制、八進制,十六進制的參數(shù),布爾型可以接收下面列出參數(shù)
1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False
Duration
類型的參數(shù)接收可以被time.ParseDuration()
解析的參數(shù)。
flag是怎么解析參數(shù)的?
我們知道flag庫是用于命令行解析的,但其內(nèi)部是怎么解析的呢?下面我們來分析一下
一個命令行參數(shù)包含以下四個部分:
- 接收參數(shù)的變量
- 參數(shù)名稱
- 默認值
- 參數(shù)說明
所以flag設(shè)置命令行參數(shù)的函數(shù)有四個參數(shù),比如:
var p int flag.IntVar(&p,"port",3306,"數(shù)據(jù)庫端口")
flag內(nèi)部有一個名稱CommandLine的變量,其類型為FlagSet,如:
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
FlagSet就是一個命令行參數(shù)的集合體,當我們調(diào)用諸如IntVar
這類的函數(shù)時,就是將命令行的默認值
、參數(shù)說明
,參數(shù)名稱
,接收參數(shù)的變量
等信息告訴flag庫,而flag內(nèi)部會讓CommandLine來處理,用這些信息創(chuàng)建Flag類型的變量,將添加到這個集合體中。
flag := &Flag{name, usage, value, value.String()}
最后,當我們調(diào)用flag.Parse函數(shù)時,實際就是調(diào)用FlagSet結(jié)構(gòu)體的Parse函數(shù)將命令參數(shù)解析到變量中,flag.Parse函數(shù)代碼如下:
func Parse() { CommandLine.Parse(os.Args[1:]) }
從上面的代碼我們也可以看出來,F(xiàn)lagSet的Parse函數(shù)最終是通過獲取os.Args數(shù)組的數(shù)據(jù)來解析命令行參數(shù)的。
即然我們知道flag是通過類型為FlagSet的變量CommandLine來處理命令行參數(shù)的,那其實我們也可以自己創(chuàng)建一個FlagSet類型的變量來處理命令行參數(shù),
所以我們可以將上面的例改成下面的樣子:
package main import ( "flag" "fmt" "os" ) func main() { //自己創(chuàng)建一個命令行參數(shù)的集合 var flagSet = flag.NewFlagSet("my flag", flag.ExitOnError) host := flagSet.String("host", "", "數(shù)據(jù)庫地址") dbName := flagSet.String("db_name", "", "數(shù)據(jù)庫名稱") user := flagSet.String("user", "", "數(shù)據(jù)庫用戶") password := flagSet.String("password", "", "數(shù)據(jù)庫密碼") port := flagSet.Int("port", 3306, "數(shù)據(jù)庫端口") //解析命令行參數(shù),從os.Args的第二個元素開始,第一個元素是命令本身 flagSet.Parse(os.Args[1:]) fmt.Printf("數(shù)據(jù)庫地址:%s\n", *host) fmt.Printf("數(shù)據(jù)庫名稱:%s\n", *dbName) fmt.Printf("數(shù)據(jù)庫用戶:%s\n", *user) fmt.Printf("數(shù)據(jù)庫密碼:%s\n", *password) fmt.Printf("數(shù)據(jù)庫端口:%d\n", *port) }
另外,我們已經(jīng)知道了flag解析參數(shù)的來源是os.Args這樣的字符串數(shù)組,那我們也可以模擬一個這樣的數(shù)組,將數(shù)組解析到變量之中,而不需要去解析os.Args數(shù)組,
下面的例子就是這樣做的:
package main import ( "flag" "fmt" ) func main() { //模擬os.Args數(shù)組,定義一個參數(shù)數(shù)組 var params = []string{"-host", "127.0.0.1", "-db_name", "test", "-user", "test", "-password", "abcdef", "-port", "13306"} var flagSet = flag.NewFlagSet("my flag", flag.ExitOnError) host := flagSet.String("host", "", "數(shù)據(jù)庫地址") dbName := flagSet.String("db_name", "", "數(shù)據(jù)庫名稱") user := flagSet.String("user", "", "數(shù)據(jù)庫用戶") password := flagSet.String("password", "", "數(shù)據(jù)庫密碼") port := flagSet.Int("port", 3306, "數(shù)據(jù)庫端口") //解析自定義的參數(shù)數(shù)組 flagSet.Parse(params) fmt.Printf("數(shù)據(jù)庫地址:%s\n", *host) fmt.Printf("數(shù)據(jù)庫名稱:%s\n", *dbName) fmt.Printf("數(shù)據(jù)庫用戶:%s\n", *user) fmt.Printf("數(shù)據(jù)庫密碼:%s\n", *password) fmt.Printf("數(shù)據(jù)庫端口:%d\n", *port) }
運行程序,在命令后面不需要跟命令行參數(shù),如下:
go run main.go
運行后結(jié)果如下:
數(shù)據(jù)庫地址:127.0.0.1
數(shù)據(jù)庫名稱:test
數(shù)據(jù)庫用戶:test
數(shù)據(jù)庫密碼:abcdef
數(shù)據(jù)庫端口:13306
自定義數(shù)據(jù)類型
如果flag提供的數(shù)據(jù)類型不能滿足我們的需要,我們也可以自定義類型,自定義類型需要實現(xiàn)flag中的Value接口,該接口定義如下:
type Value interface { String() string Set(string) error }
Value
類型的String()用于打印數(shù)值,而Set方法則用于flag包將命令行參數(shù)設(shè)置到Value
類型中。
下面是一個自定義類型的示例程序:
package main import ( "flag" "fmt" "strings" ) type Users []string func (u *Users) Set(val string) error { *u = strings.Split(val, ",") return nil } func (u *Users) String() string { str := "[" for _, v := range *u { str += v } return str + "]" } func main() { var u Users flag.Var(&u, "u", "用戶列表") flag.Parse() for _, v := range u { fmt.Println(v) } }
運行結(jié)果:
go run main.go -u=小明,小張,小紅,小剛
小明
小張
小紅
小剛
從上面的示例中我們可以總結(jié)自定義類型的幾個步驟:
- 定義一個實現(xiàn)flag.Value接口的類型,并實現(xiàn)String和Set方法。
- 使用flag.Var函數(shù)將類型綁定到類型參數(shù)。
- 調(diào)用flag.Parse()解析命令行參數(shù)。
短選項
我們在使用Linux命令的時候,發(fā)現(xiàn)很多命令的參數(shù)是有分短選項和長選項的,不過flag庫并不支持短選項;當然也有變通的方式,比如我們可以自己定義一個長選項和短選項,
如:
var port int flag.IntVar(&port, "p", 3306, "數(shù)據(jù)庫端口") flag.IntVar(&port, "port", 3306, "數(shù)據(jù)庫端口") flag.Parse() fmt.Println(port)
上面的程序中,我們定義了p
和port
兩個參數(shù),并將其綁定到變量port
,因此通過下面兩條命令都可以獲取參數(shù):
$ go run main.go -p 111 $ go run main.go -port 111
小結(jié)
在這篇文章中,我們?nèi)嬷v解了flag庫,看完這篇文章,相信你可以學習到以下幾點:
- 在Go程序員中如何使用flag庫解析命令行參數(shù)。
- flag庫解析命令行參數(shù)的原理。
- 怎么讓flag庫支持我們自定義的數(shù)據(jù)類型。
- 怎么定義命令行參數(shù)短選項。
到此這篇關(guān)于關(guān)于Golang標準庫flag的全面講解的文章就介紹到這了,更多相關(guān)Golang標準庫flag內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go將request?body綁定到不同的結(jié)構(gòu)體中教程
這篇文章主要為大家介紹了go將request?body綁定到不同的結(jié)構(gòu)體中教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10使用go語言實現(xiàn)查找兩個數(shù)組的異同操作
這篇文章主要介紹了使用go語言實現(xiàn)查找兩個數(shù)組的異同操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go語言使用HTTP包創(chuàng)建WEB服務(wù)器的方法
這篇文章主要介紹了Go語言使用HTTP包創(chuàng)建WEB服務(wù)器的方法,結(jié)合實例形式分析了Go語言基于HTTP包創(chuàng)建WEB服務(wù)器客戶端與服務(wù)器端的實現(xiàn)方法與相關(guān)注意事項,需要的朋友可以參考下2016-07-07golang 網(wǎng)絡(luò)框架之gin的使用方法
這篇文章主要介紹了golang 網(wǎng)絡(luò)框架之gin的使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11