Go標準庫flag包的“小陷阱”

piny發表於2021-09-09

圖片描述

,這意味著Go標準庫可開箱即用,為Gopher提供了功能豐富的常用工具包,足以應付多數日常開發所需。尤其在Go語言擅長的領域,Go標準庫工具包更是有著廣泛的應用。下圖是[Go官方2020年使用者調查]的結果:

圖片描述

我們看到cli([command-line interface])領域開發佔據了Go語言應用的Top2位置,僅次於開發API/RPC服務。而普通cli應用的開發總是離不開標準庫的flag包。

flag包估計是很多gopher入門go語言的必經之路。flag使用起來十分簡單,功能也不差,常規的命令列程式的flag形式它都支援,比如下面這個示例程式:

// flag_demo1.go
package main

import (
	"flag"
	"fmt"
)

var (
	n = flag.Int("n", 1234, "help message for flag n")
)

func main() {
	flag.Parse()
	fmt.Printf("n=%dn", *n)
}

flag_demo1僅支援一個cmd flag: -n。我們可以像下面這樣使用flag_demo1這個cli程式,為變數n傳值:

$go build flag_demo1.go

$./flag_demo1          
n=1234 //預設值

$./flag_demo1 -n 1111
n=1111

$./flag_demo1 --n 1111
n=1111 // --n和-n是等價的

$./flag_demo1 -n=2222 
n=2222

$./flag_demo1 --n=2222
n=2222

我們看到,我們可以使用下面四種形式為一個整型flag變數傳引數:

  • -n value
  • –n value
  • -n=value
  • –n=value

無論使用哪種形式,它們起到的效果是等價的。

但是當我們將flag放置在cli應用的最後面時,我們要小心了:

$./flag_demo1 show -n=2222
n=1234

我們看到雖然我們在命令列因公flag_demo1的引數列表中進行了-n=2222的引數傳遞,但flag_demo1的flag包直接無視了這次引數傳遞,而將變數n置為預設值1234了。

這是因為flag包的命令列引數的解析邏輯是:當碰到第一個非flag引數時,便停止解析。上面命令列執行時傳入的“show”並非flag_demo1的flag引數,因此flag包就會在解析完show後停止後面命令列引數(-n=2222)的解析,於是上述命令列就等價於:

$./flag_demo1 show
n=1234

那麼n=1234就不足為奇了!這被我稱為flag包的第一個“小陷阱”。不僅像“show”這樣的非flag引數可以阻斷flag包對命令列引數列表的繼續解析,單獨存在的“-”和“–”也具有同樣的“阻斷功能”:

$./flag_demo1 -- -n=2222  
n=1234
$./flag_demo1 - -n=2222 
n=1234

我們也常在命令列flag引數中使用bool類的引數值,比如下面示例:

// flag_demo2.go

package main

import (
	"flag"
	"fmt"
)

var (
	n  = flag.Int("n", 1234, "int value for flag n")
	b1 = flag.Bool("b1", false, "bool value for flag b1")
	b2 = flag.Bool("b2", false, "bool value for flag b2")
)

func main() {
	flag.Parse()
	fmt.Printf("n=%dn", *n)
	fmt.Printf("b1=%tn", *b1)
	fmt.Printf("b2=%tn", *b2)
}

這個示例中有兩個bool型flag引數和一個int型flag引數,我們來執行一下該cli應用:

$go build flag_demo2.go
$./flag_demo2 -b1 true -b2 true -n 2222
n=1234
b1=true
b2=false

執行的輸出似乎與預期結果不符啊!為什麼b2變數的值依舊為false,變數n的值為啥不是2222?難道在多個flag引數下,flag包有bug?其實不是的!

問題就在於bool型別flag引數的特殊性。由於一些原因,bool型別flag引數不支援“-arg value”形式,只支援下面兩種形式:

-arg
-arg=value

我們按bool型別flag引數的正確傳遞方法再執行一下上面的flag_demo2:

$./flag_demo2 -b1=true -b2=true -n 2222
n=2222
b1=true
b2=true

這回的輸出與預期吻合。

但細心的朋友可能會發現,之前的錯誤用法:

$./flag_demo2 -b1 true -b2 true -n 2222

十分有迷惑性!因為變數b1的輸出值是符合預期的,這讓人誤以為flag引數的傳遞方法是正確無誤的。這種“錯覺”讓gopher不知不覺地掉入了**flag包的第二個“陷阱”**中。而上面的錯誤flag引數值傳遞實質上等價於:

$./flag_demo2 -b1

這就是為什麼b1=true,而b2和n均為預設值的原因了!

flag包是我們日常最廣泛使用的標準庫包之一,因此務必瞭解flag包可能被誤用的情況,別掉入flag包的“小陷阱”中!陷阱雖小,出事是大,希望這裡的分享能幫助大家在日常繞過這些“陷阱”!


Go技術專欄“”正在慕課網火熱熱銷中!本專欄主要滿足廣大gopher關於Go語言進階的需求,圍繞如何寫出地道且高質量Go程式碼給出50條有效實踐建議,上線後收到一致好評!歡迎大家訂
閱!

圖片描述

我的網課“”在慕課網熱賣中,歡迎小夥伴們訂閱學習!

圖片描述


講師主頁:
講師部落格:
專欄:
實戰課:
免費課:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3034/viewspace-2797836/,如需轉載,請註明出處,否則將追究法律責任。

相關文章