title: 【轉載】Go 語言標準庫之命令行參數的解析:flag 庫詳解
date: 2021-09-07 13:12:00
comment: false
toc: true
category:
- Golang
tags: - 轉載
- Golang
- 命令行
- 參數
- 解析
- flag
- 詳解
- 標準庫
本文轉載自:Go 語言標準庫之命令行參數的解析:flag 庫詳解
在 Golang 程序中有很多種方法來處理命令行參數。
簡單的情況下可以不使用任何庫,直接使用 os.Args
package main
import (
"fmt"
"os"
)
func main() {
//os.Args是一个[]string
if len(os.Args) > 0 {
for index, arg := range os.Args {
fmt.Printf("args[%d]=%v\n", index, arg)
}
}
}
試著運行一下,第一個參數是執行文件的路徑。
$ go run demo.go hello world hello golang
args[0]=/var/folders/72/lkr7ltfd27lcf36d75jdyjr40000gp/T/go-build187785213/b001/exe/demo
args[1]=hello
args[2]=world
args[3]=hello
args[4]=golang
從上面你可以看到,os.Args
只能處理簡單的參數,而且對於參數的位置有嚴格的要求。對於一些比較複雜的場景,就需要你自己定義解析規則,非常麻煩。
如果真的遇上了所謂的複雜場景,那麼還可以使用 Golang 的標準庫 flag 包來處理命令行參數。
本文將介紹 Golang 標準庫中 flag 包的用法。
參數種類#
根據參數是否為布爾型,可以分為兩種:
- 布爾型參數:如
--debug
,後面不用再接具體的值,指定就為 True,不指定就為 False 非布爾型參數 - 非布爾型參數:非布爾型,有可能是 int,string 等其他類型,如
--name jack
,後面可以接具體的參數值
根據參數名的長短,還可以分為:
- 長參數:比如
--name jack
就是一個長參數,參數名前有兩個-
- 短參數:通常為一個或兩個字母(是對應長參數的簡寫),比如
-n
,參數名前只有一個-
入門示例#
我先用一個字符串類型的參數的示例,拋磚引玉
package main
import (
"flag"
"fmt"
)
func main(){
var name string
flag.StringVar(&name, "name", "jack", "your name")
flag.Parse() // 解析參數
fmt.Println(name)
}
flag.StringVar
定義了一個字符串參數,它接收幾個參數
- 第一个参数 :接收值後,存放在哪個變量裡,需為指針
- 第二个参数 :在命令行中使用的參數名,比如
--name jack
裡的 name - 第三个参数 :若命令行中未指定該參數值,那麼默認值為
jack
- 第四个参数:記錄這個參數的用途或意義
運行以上程序,輸出如下
$ go run demo.go
jack
$ go run demo.go --name wangbm
wangbm
改進一下#
如果你的程序只接收很少的幾個參數時,上面那樣寫也沒有什麼問題。
但一旦參數數量多了以後,一大堆參數解析的代碼堆積在 main 函數裡,影響代碼的可讀性、美觀性。
建議將參數解析的代碼放入 init
函數中,init
函數會先於 main
函數執行。
package main
import (
"flag"
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "jack", "your name")
}
func main(){
flag.Parse()
fmt.Println(name)
}
參數類型#
當你在命令行中指定了參數,Go 如何解析這個參數,轉化成何種類型,是需要你事先定義的。
不同的參數,對應著 flag
中不同的方法。
下面分別講講不同的參數類型,都該如何定義。
布爾型#
實現效果 :當不指定 --debug
時,debug 的默認值為 false,你一指定 --debug
,debug 為賦值為 true。
var debug bool
func init() {
flag.BoolVar(&debug, "debug", false, "是否開啟 DEBUG 模式")
}
func main(){
flag.Parse()
fmt.Println(debug)
}
運行後,執行結果如下
$ go run main.go
false
$ go run main.go --debug
true
數值型#
定義一個 age 參數,不指定默認為 18
var age int
func init() {
flag.IntVar(&age, "age", 18, "你的年齡")
}
func main(){
flag.Parse()
fmt.Println(age)
}
運行後,執行結果如下
$ go run main.go
18
$ go run main.go --age 20
20
int64
、 uint
和 float64
類型分別對應 Int64Var 、 UintVar、Float64Var 方法,也是同理,不再贅述。
字符串#
定義一個 name 參數,不指定默認為 jack
var name string
func init() {
flag.StringVar(&name, "name", "jack", "你的名字")
}
func main(){
flag.Parse()
fmt.Println(name)
}
運行後,執行結果如下
$ go run main.go
jack
$ go run main.go --name wangbm
wangbm
時間類型#
定義一個 interval 參數,不指定默認為 1s
var interval time.Duration
func init() {
flag.DurationVar(&interval, "interval", 1 * time.Second, "循環間隔")
}
func main(){
flag.Parse()
fmt.Println(interval)
}
驗證效果如下
$ go run main.go
1s
$ go run main.go --interval 2s
2s
自定義類型#
flag 包支持的類型有 Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。
這些類型的參數被封裝到其對應的後端類型中,比如 Int 類型的參數被封裝為 intValue,String 類型的參數被封裝為 stringValue。
這些後端的類型都實現了 flag.Value 接口,因此可以把一個命令行參數抽象為一個 Flag 類型的實例。下面是 Value 接口和 Flag 類型的代碼:
type Value interface {
String() string
Set(string) error
}
// Flag 類型
type Flag struct {
Name string // name as it appears on command line
Usage string // help message
Value Value // value as set 是個 interface,因此可以是不同類型的實例。
DefValue string // default value (as text); for usage message
}
func Var(value Value, name string, usage string) {
CommandLine.Var(value, name, usage)
}
想要實現自定義類型的參數,其實只要 Var 函數的第一個參數對象實現 flag.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) String() string {
return strings.Join([]string(*s), ",")
}
比如我想實現如下效果,傳入的參數是一個字符串,以逗號分隔,flag 的解析時將其轉成 slice。
$ go run demo.go -members "Jack,Tom"
[Jack Tom]
那我可以這樣子編寫代碼
var members []string
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) String() string {
return strings.Join([]string(*s), ",")
}
func init() {
flag.Var(newSliceValue([]string{}, &members), "members", "會員列表")
}
func main(){
flag.Parse()
fmt.Println(members)
}
有的朋友 可能會對 (*sliceValue)(p)
這行代碼有所疑問,這意味著什麼呢?
關於這個,其實之前在 【2.9 詳細圖解:靜態類型與動態類型】有講過,忘記了可以前往複習。
長短選項#
flag 包,在使用上,其實並沒有沒有長短選項之別,你可以看下面這個例子
package main
import (
"flag"
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "明哥", "你的名字")
}
func main(){
flag.Parse()
fmt.Println(name)
}
通過指定如下幾種參數形式
$ go run main.go
明哥
$ go run main.go --name jack
jack
$ go run main.go -name jack
jack
一個 -
和兩個 -
執行結果是相同的。
那麼再加一個呢?
終於報錯了。說明最多只能指定兩個 -
$ go run main.go ---name jack
bad flag syntax: ---name
Usage of /tmp/go-build245956022/b001/exe/main:
-name string
你的名字 (default "明哥")
exit status 2