<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          40. 命令行參數(shù)的解析:flag 庫詳解

          共 5274字,需瀏覽 11分鐘

           ·

          2020-12-08 18:00

          點擊上方“Go編程時光”,選擇“加為星標

          第一時間關注Go技術干貨!



          在 Golang 程序中有很多種方法來處理命令行參數(shù)。

          簡單的情況下可以不使用任何庫,直接使用 os.Args

          package?main

          import?(
          ????"fmt"
          ????"os"
          )

          func?main()?{
          ????if?len(os.Args)?>?0?{
          ????????for?index,?arg?:=?range?os.Args?{
          ????????????fmt.Printf("args[%d]=%v\n",?index,?arg)
          ????????}
          ????}
          }

          試著運行一下,第一個參數(shù)是執(zhí)行文件的路徑。

          $?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 只能處理簡單的參數(shù),而且對于參數(shù)的位置有嚴格的要求。對于一些比較復雜的場景,就需要你自己定義解析規(guī)則,非常麻煩。

          如果真的遇上了所謂的復雜場景,那么還可以使用 Golang 的標準庫 flag 包來處理命令行參數(shù)。

          本文將介紹 Golang 標準庫中 flag 包的用法。

          1. 參數(shù)種類

          根據(jù)參數(shù)是否為布爾型,可以分為兩種:

          • 布爾型參數(shù):如 --debug,后面不用再接具體的值,指定就為 True,不指定就為 False非布爾型參數(shù)

          • 非布爾型參數(shù):非布爾型,有可能是int,string 等其他類型,如 --name jack ,后面可以接具體的參數(shù)值

          根據(jù)參數(shù)名的長短,還可以分為:

          • 長參數(shù):比如 --name jack 就是一個長參數(shù),參數(shù)名前有兩個 -

          • 短參數(shù):通常為一個或兩個字母(是對應長參數(shù)的簡寫),比如 -n ,參數(shù)名前只有一個 -

          2. 入門示例

          我先用一個字符串類型的參數(shù)的示例,拋磚引玉

          package?main

          import?(
          ????"flag"
          ????"fmt"
          )

          func?main(){
          ????var?name?string
          ????flag.StringVar(&name,?"name",?"jack",?"your?name")

          flag.Parse()??//?解析參數(shù)
          ????fmt.Println(name)
          }

          flag.StringVar 定義了一個字符串參數(shù),它接收幾個參數(shù)

          • 第一個參數(shù) :接收值后,存放在哪個變量里,需為指針

          • 第二個參數(shù) :在命令行中使用的參數(shù)名,比如 --name jack 里的 name

          • 第三個參數(shù) :若命令行中未指定該參數(shù)值,那么默認值為 jack

          • 第四個參數(shù):記錄這個參數(shù)的用途或意義

          運行以上程序,輸出如下

          $ go run demo.go
          jack

          $?go?run?demo.go?--name?wangbm
          wangbm

          3. 改進一下

          如果你的程序只接收很少的幾個參數(shù)時,上面那樣寫也沒有什么問題。

          但一旦參數(shù)數(shù)量多了以后,一大堆參數(shù)解析的代碼堆積在 main 函數(shù)里,影響代碼的可讀性、美觀性。

          建議將參數(shù)解析的代碼放入 init 函數(shù)中,init 函數(shù)會先于 main 函數(shù)執(zhí)行。

          package?main

          import?(
          ????"flag"
          ????"fmt"
          )

          var?name?string

          func?init()??{
          ????flag.StringVar(&name,?"name",?"jack",?"your?name")
          }

          func?main(){
          ????flag.Parse()
          ????fmt.Println(name)
          }

          4. 參數(shù)類型

          當你在命令行中指定了參數(shù),Go 如何解析這個參數(shù),轉化成何種類型,是需要你事先定義的。

          不同的參數(shù),對應著 flag 中不同的方法。

          下面分別講講不同的參數(shù)類型,都該如何定義。

          布爾型

          實現(xiàn)效果:當不指定 --debug 時,debug 的默認值為 false,你一指定 --debug,debug 為賦值為 true。

          var?debug?bool

          func?init()??{
          ????flag.BoolVar(&debug,?"debug",?false,?"是否開啟?DEBUG?模式")
          }

          func?main(){
          ????flag.Parse()
          ????fmt.Println(debug)
          }

          運行后,執(zhí)行結果如下

          $?go?run?main.go?
          false
          $?go?run?main.go?--debug
          true

          數(shù)值型

          定義一個 age 參數(shù),不指定默認為 18

          var?age?int

          func?init()??{
          ????flag.IntVar(&age,?"age",?18,?"你的年齡")
          }

          func?main(){
          ????flag.Parse()
          ????fmt.Println(age)
          }

          運行后,執(zhí)行結果如下

          $?go?run?main.go?
          18

          $?go?run?main.go?--age?20
          20

          int64uintfloat64 類型分別對應 Int64Var 、 UintVar、Float64Var 方法,也是同理,不再贅述。

          字符串

          定義一個 name參數(shù),不指定默認為 jack

          var?name?string

          func?init()??{
          ????flag.StringVar(&name,?"name",?"jack",?"你的名字")
          }

          func?main(){
          ????flag.Parse()
          ????fmt.Println(name)
          }

          運行后,執(zhí)行結果如下

          $?go?run?main.go?
          jack

          $?go?run?main.go?--name?wangbm
          wangbm

          時間類型

          定義一個 interval 參數(shù),不指定默認為 1s

          var?interval?time.Duration

          func?init()??{
          ????flag.DurationVar(&interval,?"interval",?1?*?time.Second,?"循環(huán)間隔")
          }

          func?main(){
          ????flag.Parse()
          ????fmt.Println(interval)
          }

          驗證效果如下

          $?go?run?main.go?
          1s

          $?go?run?main.go?--interval?2s
          2s

          5. 自定義類型

          flag 包支持的類型有 Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。

          這些類型的參數(shù)被封裝到其對應的后端類型中,比如 Int 類型的參數(shù)被封裝為 intValue,String 類型的參數(shù)被封裝為 stringValue。

          這些后端的類型都實現(xiàn)了 flag.Value 接口,因此可以把一個命令行參數(shù)抽象為一個 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)
          }

          想要實現(xiàn)自定義類型的參數(shù),其實只要 Var 函數(shù)的第一個參數(shù)對象實現(xiàn) flag.Value接口即可

          type?sliceValue?[]string


          func?newSliceValue(vals?[]string,?p?*[]string)?*sliceValue?{
          ????*p?=?vals
          ????return?(*sliceValue)(p)
          }

          func?(s?*sliceValue)?Set(val?string)?error?{
          ?????????//?如何解析參數(shù)值
          ????*s?=?sliceValue(strings.Split(val,?","))
          ????return?nil
          }

          func?(s?*sliceValue)?String()?string?{
          ????return?strings.Join([]string(*s),?",")
          }

          比如我想實現(xiàn)如下效果,傳入的參數(shù)是一個字符串,以逗號分隔,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?{
          ?????????//?如何解析參數(shù)值
          ????*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 詳細圖解:靜態(tài)類型與動態(tài)類型】有講過,忘記了可以前往復習。

          6. 長短選項

          flag 包,在使用上,其實并沒有沒有長短選項之別,你可以看下面這個例子

          package?main

          import?(
          ????"flag"
          ????"fmt"
          )

          var?name?string

          func?init()??{
          ????flag.StringVar(&name,?"name",?"明哥",?"你的名字")
          }

          func?main(){
          ????flag.Parse()
          ????fmt.Println(name)
          }

          通過指定如下幾種參數(shù)形式

          $?go?run?main.go?
          明哥
          $?go?run?main.go?--name?jack
          jack
          $?go?run?main.go?-name?jack
          jack

          一個 - 和兩個 - 執(zhí)行結果是相同的。

          那么再加一個呢?

          終于報錯了。說明最多只能指定兩個 -

          $?go?run?main.go?---name?jack
          bad?flag?syntax:?---name
          Usage?of?/tmp/go-build245956022/b001/exe/main:
          ??-name?string
          ????????你的名字?(default?"明哥")
          exit?status?2



          7. 總結一下

          flag 在絕大多數(shù)場景下,它是夠用的,但如果要支持更多的命令傳入格式,flag 可能并不是最好的選擇。

          那些在標準庫不能解決的場景,往往會有相應的Go愛好者提供第三方解決方案。我所了解到的 cobra 就是一個非常不錯的庫。

          它能夠支持 flag 不能支持的功能,比如 支持短選項支持子命令 等等,后面找個機會再好好寫一下。





          明哥原創(chuàng)文都已傳至 Github:https://github.com/iswbm/GolangCodingTime

          本文永久博客鏈接https://iswbm.com/167.html



          喜歡本教程系列的同學
          歡迎長按下圖訂閱!

          ???



          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩av一卡电影在线观看 | 日本激情视频 | 免费看黄篇视频 | 韩国一级一级免费 | 韩国三级HD中文久久精品车子 |