我一直在想,有什麼方式可讓人比較輕易地保持每日學習,持續輸出的狀態。寫博客是一種方式,但不是天天都有想寫的,值得寫的東西。
有時候一個技術比較複雜,寫博客的時候常常會寫着寫着發現本身的理解有誤差,或者細節尚未徹底掌握,要去查資料,瞭解了以後又繼續寫,如此反覆。
這樣會致使一篇博客的耗時過長。python
我在天天瀏覽思否、掘金和Github的過程當中,發現一些比較好的想法,有JS 每日一題,NodeJS 每日一庫,天天一道面試題等等等等。
https://github.com/parro-it/awesome-micro-npm-packages這個倉庫收集 NodeJS 小型庫,一天看一個不是夢!這也是我這個系列的靈感。
我計劃天天學習一個 Go 語言的庫,輸出一篇介紹型的博文。天天一庫固然是理想狀態,我心中的預期是一週 3-5 個。git
今天是第一天,咱們從一個基礎庫聊起————Go 標準庫中的flag
。github
flag
用於解析命令行選項。有過類 Unix 系統使用經驗的童鞋對命令行選項應該不陌生。例如命令ls -al
列出當前目錄下全部文件和目錄的詳細信息,其中-al
就是命令行選項。golang
命令行選項在實際開發中很經常使用,特別是在寫工具的時候。面試
redis-server ./redis.conf
以當前目錄下的配置文件redis.conf
啓動 Redis 服務器;python -m SimpleHTTPServer 8080
啓動一個 HTTP 服務器,監聽 8080 端口。若是不指定,則默認監聽 8000 端口。學習一個庫的第一步固然是使用它。咱們先看看flag
庫的基本使用:redis
package main import ( "fmt" "flag" ) var ( intflag int boolflag bool stringflag string ) func init() { flag.IntVar(&intflag, "intflag", 0, "int flag value") flag.BoolVar(&boolflag, "boolflag", false, "bool flag value") flag.StringVar(&stringflag, "stringflag", "default", "string flag value") } func main() { flag.Parse() fmt.Println("int flag:", intflag) fmt.Println("bool flag:", boolflag) fmt.Println("string flag:", stringflag) }
能夠先編譯程序,而後運行(我使用的是 Win10 + Git Bash):shell
$ go build -o main.exe main.go $ ./main.exe -intflag 12 -boolflag 1 -stringflag test
輸出:npm
int flag: 12 bool flag: true string flag: test
若是不設置某個選項,相應變量會取默認值:segmentfault
$ ./main.exe -intflag 12 -boolflag 1
輸出:服務器
int flag: 12 bool flag: true string flag: default
能夠看到沒有設置的選項stringflag
爲默認值default
。
還能夠直接使用go run
,這個命令會先編譯程序生成可執行文件,而後執行該文件,將命令行中的其它選項傳給這個程序。
$ go run main.go -intflag 12 -boolflag 1
可使用-h
顯示選項幫助信息:
$ ./main.exe -h Usage of D:\code\golang\src\github.com\darjun\cmd\flag\main.exe: -boolflag bool flag value -intflag int int flag value -stringflag string string flag value (default "default")
總結一下,使用flag
庫的通常步驟:
intflag/boolflag/stringflag
;init
方法中使用flag.TypeVar
方法定義選項,這裏的Type
能夠爲基本類型Int/Uint/Float64/Bool
,還能夠是時間間隔time.Duration
。定義時傳入變量的地址、選項名、默認值和幫助信息;main
方法中調用flag.Parse
從os.Args[1:]
中解析選項。由於os.Args[0]
爲可執行程序路徑,會被剔除。注意點:
flag.Parse
方法必須在全部選項都定義以後調用,且flag.Parse
調用以後不能再定義選項。若是按照前面的步驟,基本不會出現問題。
由於init
在全部代碼以前執行,將選項定義都放在init
中,main
函數中執行flag.Parse
時全部選項都已經定義了。
flag
庫支持三種命令行選項格式。
-flag -flag=x -flag x
-
和--
均可以使用,它們的做用是同樣的。有些庫使用-
表示短選項,--
表示長選項。相對而言,flag
使用起來更簡單。
第一種形式只支持布爾類型的選項,出現即爲true
,不出現爲默認值。
第三種形式不支持布爾類型的選項。由於這種形式的布爾選項在類 Unix 系統中可能會出現意想不到的行爲。看下面的命令:
cmd -x *
其中,*
是 shell 通配符。若是有名字爲 0、false的文件,布爾選項-x
將會取false
。反之,布爾選項-x
將會取true
。並且這個選項消耗了一個參數。
若是要顯示設置一個布爾選項爲false
,只能使用-flag=false
這種形式。
遇到第一個非選項參數(即不是以-
和--
開頭的)或終止符--
,解析中止。運行下面程序:
$ ./main.exe noflag -intflag 12
將會輸出:
int flag: 0 bool flag: false string flag: default
由於解析遇到noflag
就中止了,後面的選項-intflag
沒有被解析到。因此全部選項都取的默認值。
運行下面的程序:
$ ./main.exe -intflag 12 -- -boolflag=true
將會輸出:
int flag: 12 bool flag: false string flag: default
首先解析了選項intflag
,設置其值爲 12。遇到--
後解析終止了,後面的--boolflag=true
沒有被解析到,因此boolflag
選項取默認值false
。
解析終止以後若是還有命令行參數,flag
庫會存儲下來,經過flag.Args
方法返回這些參數的切片。
能夠經過flag.NArg
方法獲取未解析的參數數量,flag.Arg(i)
訪問位置i
(從 0 開始)上的參數。
選項個數也能夠經過調用flag.NFlag
方法獲取。
稍稍修改一下上面的程序:
func main() { flag.Parse() fmt.Println(flag.Args()) fmt.Println("Non-Flag Argument Count:", flag.NArg()) for i := 0; i < flag.NArg(); i++ { fmt.Printf("Argument %d: %s\n", i, flag.Arg(i)) } fmt.Println("Flag Count:", flag.NFlag()) }
編譯運行該程序:
$ go build -o main.exe main.go $ ./main.exe -intflag 12 -- -stringflag test
輸出:
[-stringflag test] Non-Flag Argument Count: 2 Argument 0: -stringflag Argument 1: test
解析遇到--
終止後,剩餘參數-stringflag test
保存在flag
中,能夠經過Args/NArg/Arg
等方法訪問。
整數選項值能夠接受 1234(十進制)、0664(八進制)和 0x1234(十六進制)的形式,而且能夠是負數。實際上flag
在內部使用strconv.ParseInt
方法將字符串解析成int
。
因此理論上,ParseInt
接受的格式均可以。
布爾類型的選項值能夠爲:
true
的:一、t、T、true、TRUE、True;false
的:0、f、F、false、FALSE、False。上面咱們介紹了使用flag.TypeVar
定義選項,這種方式須要咱們先定義變量,而後變量的地址。
還有一種方式,調用flag.Type
(其中Type
能夠爲Int/Uint/Bool/Float64/String/Duration
等)會自動爲咱們分配變量,返回該變量的地址。用法與前一種方式相似:
package main import ( "fmt" "flag" ) var ( intflag *int boolflag *bool stringflag *string ) func init() { intflag = flag.Int("intflag", 0, "int flag value") boolflag = flag.Bool("boolflag", false, "bool flag value") stringflag = flag.String("stringflag", "default", "string flag value") } func main() { flag.Parse() fmt.Println("int flag:", *intflag) fmt.Println("bool flag:", *boolflag) fmt.Println("string flag:", *stringflag) }
編譯並運行程序:
$ go build -o main.exe main.go $ ./main.exe -intflag 12
將輸出:
int flag: 12 bool flag: false string flag: default
除了使用時須要解引用,其它與前一種方式基本相同。
flag
庫並無顯示支持短選項,可是能夠經過給某個相同的變量設置不一樣的選項來實現。即兩個選項共享同一個變量。
因爲初始化順序不肯定,必須保證它們擁有相同的默認值。不然不傳該選項時,行爲是不肯定的。
package main import ( "fmt" "flag" ) var logLevel string func init() { const ( defaultLogLevel = "DEBUG" usage = "set log level value" ) flag.StringVar(&logLevel, "log_type", defaultLogLevel, usage) flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(shorthand)") } func main() { flag.Parse() fmt.Println("log level:", logLevel) }
編譯、運行程序:
$ go build -o main.exe main.go $ ./main.exe -log_type WARNING $ ./main.exe -l WARNING
使用長、短選項均輸出:
log level: WARNING
不傳入該選項,輸出默認值:
$ ./main.exe log level: DEBUG
除了能使用基本類型做爲選項,flag
庫還支持time.Duration
類型,即時間間隔。時間間隔支持的格式很是之多,例如"300ms"、"-1.5h"、"2h45m"等等等等。
時間單位能夠是 ns/us/ms/s/m/h/day 等。實際上flag
內部會調用time.ParseDuration
。具體支持的格式能夠參見time(需fq)庫的文檔。
package main import ( "flag" "fmt" "time" ) var ( period time.Duration ) func init() { flag.DurationVar(&period, "period", 1*time.Second, "sleep period") } func main() { flag.Parse() fmt.Printf("Sleeping for %v...", period) time.Sleep(period) fmt.Println() }
根據傳入的命令行選項period
,程序睡眠相應的時間,默認 1 秒。編譯、運行程序:
$ go build -o main.exe main.go $ ./main.exe Sleeping for 1s... $ ./main.exe -period 1m30s Sleeping for 1m30s...
除了使用flag
庫提供的選項類型,咱們還能夠自定義選項類型。咱們分析一下標準庫中提供的案例:
package main import ( "errors" "flag" "fmt" "strings" "time" ) type interval []time.Duration func (i *interval) String() string { return fmt.Sprint(*i) } func (i *interval) Set(value string) error { if len(*i) > 0 { return errors.New("interval flag already set") } for _, dt := range strings.Split(value, ",") { duration, err := time.ParseDuration(dt) if err != nil { return err } *i = append(*i, duration) } return nil } var ( intervalFlag interval ) func init() { flag.Var(&intervalFlag, "deltaT", "comma-seperated list of intervals to use between events") } func main() { flag.Parse() fmt.Println(intervalFlag) }
首先定義一個新類型,這裏定義類型interval
。
新類型必須實現flag.Value
接口:
// src/flag/flag.go type Value interface { String() string Set(string) error }
其中String
方法格式化該類型的值,flag.Parse
方法在執行時遇到自定義類型的選項會將選項值做爲參數調用該類型變量的Set
方法。
這裏將以,
分隔的時間間隔解析出來存入一個切片中。
自定義類型選項的定義必須使用flag.Var
方法。
編譯、執行程序:
$ go build -o main.exe main.go $ ./main.exe -deltaT 30s [30s] $ ./main.exe -deltaT 30s,1m,1m30s [30s 1m0s 1m30s]
若是指定的選項值非法,Set
方法返回一個error
類型的值,Parse
執行終止,打印錯誤和使用幫助。
$ ./main.exe -deltaT 30x invalid value "30x" for flag -deltaT: time: unknown unit x in duration 30x Usage of D:\code\golang\src\github.com\darjun\go-daily-lib\flag\self-defined\main.exe: -deltaT value comma-seperated list of intervals to use between events
有時候選項並非經過命令行傳遞的。例如,從配置表中讀取或程序生成的。這時候可使用flag.FlagSet
結構的相關方法來解析這些選項。
實際上,咱們前面調用的flag
庫的方法,都會間接調用FlagSet
結構的方法。flag
庫中定義了一個FlagSet
類型的全局變量CommandLine
專門用於解析命令行選項。
前面調用的flag
庫的方法只是爲了提供便利,它們內部都是調用的CommandLine
的相應方法:
// src/flag/flag.go var CommandLine = NewFlagSet(os.Args[0], ExitOnError) func Parse() { CommandLine.Parse(os.Args[1:]) } func IntVar(p *int, name string, value int, usage string) { CommandLine.Var(newIntValue(value, p), name, usage) } func Int(name string, value int, usage string) *int { return CommandLine.Int(name, value, usage) } func NFlag() int { return len(CommandLine.actual) } func Arg(i int) string { return CommandLine.Arg(i) } func NArg() int { return len(CommandLine.args) }
一樣的,咱們也能夠本身建立FlagSet
類型變量來解析選項。
package main import ( "flag" "fmt" ) func main() { args := []string{"-intflag", "12", "-stringflag", "test"} var intflag int var boolflag bool var stringflag string fs := flag.NewFlagSet("MyFlagSet", flag.ContinueOnError) fs.IntVar(&intflag, "intflag", 0, "int flag value") fs.BoolVar(&boolflag, "boolflag", false, "bool flag value") fs.StringVar(&stringflag, "stringflag", "default", "string flag value") fs.Parse(args) fmt.Println("int flag:", intflag) fmt.Println("bool flag:", boolflag) fmt.Println("string flag:", stringflag) }
NewFlagSet
方法有兩個參數,第一個參數是程序名稱,輸出幫助或出錯時會顯示該信息。第二個參數是解析出錯時如何處理,有幾個選項:
ContinueOnError
:發生錯誤後繼續解析,CommandLine
就是使用這個選項;ExitOnError
:出錯時調用os.Exit(2)
退出程序;PanicOnError
:出錯時產生 panic。隨便看一眼flag
庫中的相關代碼:
// src/flag/flag.go 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 }
與直接使用flag
庫的方法有一點不一樣,FlagSet
調用Parse
方法時須要顯示傳入字符串切片做爲參數。由於flag.Parse
在內部調用了CommandLine.Parse(os.Args[1:])
。
示例代碼都放在GitHub上了。
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~
本文由博客一文多發平臺 OpenWrite 發佈!