banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

【轉載】Go語言標準庫之命令行參數的解析:flag 庫詳解

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  

int64uintfloat64 類型分別對應 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  
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。