《Go語言四十二章經(jīng)》第三十四章 命令行 flag 包
作者:李驍
34.1 命令行
寫命令行程序時需要對命令參數(shù)進(jìn)行解析,這時我們可以使用os庫孙蒙。os庫可以通過變量Args來獲取命令參數(shù)斋射,os.Args返回一個字符串?dāng)?shù)組禾进,其中第一個參數(shù)就是執(zhí)行文件本身硬鞍。
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args)
}
編譯執(zhí)行后執(zhí)行
$ ./cmd -user="root"
[./cmd -user=root]
這種方式對于簡單的參數(shù)格式還能使用呼奢,一旦面對復(fù)雜的參數(shù)格式化戳,比較費時費勁单料,所以這時我們會選擇flag庫。
34.2 flag包
Go提供了flag包点楼,可以很方便的操作命名行參數(shù)扫尖,下面介紹下flag的用法。
幾個概念:
1)命令行參數(shù)(或參數(shù)):是指運(yùn)行程序提供的參數(shù)
2)已定義命令行參數(shù):是指程序中通過flag.Xxx等這種形式定義了的參數(shù)
3)非flag(non-flag)命令行參數(shù)(或保留的命令行參數(shù)):先可以簡單理解為flag包不能解析的參數(shù)
package main
import (
"flag"
"fmt"
"os"
)
var (
h, H bool
v bool
q *bool
D string
Conf string
)
func init() {
flag.BoolVar(&h, "h", false, "幫助信息")
flag.BoolVar(&h, "H", false, "幫助信息")
flag.BoolVar(&v, "v", false, "顯示版本號")
//
flag.StringVar(&D, "D", "deamon", "set descripton ")
flag.StringVar(&Conf, "Conf", "/dev/conf/cli.conf", "set Conf filename ")
// 另一種綁定方式
q = flag.Bool("q", false, "退出程序")
// 像flag.Xxx函數(shù)格式都是一樣的掠廓,第一個參數(shù)表示參數(shù)名稱换怖,
// 第二個參數(shù)表示默認(rèn)值,第三個參數(shù)表示使用說明和描述蟀瞧。
// flag.XxxVar這樣的函數(shù)第一個參數(shù)換成了變量地址狰域,
// 后面的參數(shù)和flag.Xxx是一樣的。
// 改變默認(rèn)的 Usage
flag.Usage = usage
flag.Parse()
var cmd string = flag.Arg(0)
fmt.Printf("-----------------------\n")
fmt.Printf("cli non=flags : %s\n", cmd)
fmt.Printf("q: %b\n", *q)
fmt.Printf("descripton: %s\n", D)
fmt.Printf("Conf filename : %s\n", Conf)
fmt.Printf("-----------------------\n")
fmt.Printf("there are %d non-flag input param\n", flag.NArg())
for i, param := range flag.Args() {
fmt.Printf("#%d :%s\n", i, param)
}
}
func main() {
flag.Parse()
if h || H {
flag.Usage()
}
}
func usage() {
fmt.Fprintf(os.Stderr, `CLI: 8.0
Usage: Cli [-hvq] [-D descripton] [-Conf filename]
`)
flag.PrintDefaults()
}
flag包實現(xiàn)了命令行參數(shù)的解析黄橘,大致需要幾個步驟:
一:flag參數(shù)定義或綁定
定義flags有兩種方式:
1)flag.Xxx()兆览,其中Xxx可以是Int、String等塞关;返回一個相應(yīng)類型的指針抬探,如:
var ip = flag.Int("flagname", 1234, "help message for flagname")
2)flag.XxxVar(),將flag綁定到一個變量上帆赢,如:
var flagvar int
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
另外小压,還可以創(chuàng)建自定義flag,只要實現(xiàn)flag.Value接口即可(要求receiver是指針)椰于,這時候可以通過如下方式定義該flag:
flag.Var(&flagVal, "name", "help message for flagname")
命令行flag的語法有如下三種形式:
-flag // 只支持bool類型
-flag=x
-flag x // 只支持非bool類型
二:flag參數(shù)解析
在所有的flag定義完成之后怠益,可以通過調(diào)用flag.Parse()進(jìn)行解析。
根據(jù)Parse()中for循環(huán)終止的條件瘾婿,當(dāng)parseOne返回false蜻牢,nil時烤咧,Parse解析終止。
s := f.args[0]
if len(s) == 0 || s[0] != '-' || len(s) == 1 {
return false, nil
}
當(dāng)遇到單獨的一個“-”或不是“-”開始時抢呆,會停止解析煮嫌。比如:./cli – -f 或 ./cli -f
這兩種情況,-f都不會被正確解析抱虐。像這些參數(shù)昌阿,我們稱之為non-flag參數(shù)
parseOne方法中接下來是處理-flag=x,然后是-flag(bool類型)(這里對bool進(jìn)行了特殊處理)恳邀,接著是-flag x這種形式懦冰,最后,將解析成功的Flag實例存入FlagSet的actual map中谣沸。
Arg(i int)和Args()儿奶、NArg()、NFlag()
Arg(i int)和Args()這兩個方法就是獲取non-flag參數(shù)的鳄抒;NArg()獲得non-flag個數(shù);NFlag()獲得FlagSet中actual長度(即被設(shè)置了的參數(shù)個數(shù))椰弊。
flag解析遇到non-flag參數(shù)就停止了许溅。所以如果我們將non-flag參數(shù)放在最前面,flag什么也不會解析秉版,因為flag遇到了這個就停止解析了贤重。
三:分支程序
根據(jù)參數(shù)值,代碼進(jìn)入分支程序清焕,執(zhí)行相關(guān)功能并蝗。上面代碼提供了 -h 參數(shù)的功能執(zhí)行。
if h || H {
flag.Usage()
}
總體而言秸妥,從例子上看滚停,flag package很有用,但是并沒有強(qiáng)大到解析一切的程度粥惧。如果你的入?yún)⒔馕龇浅?fù)雜键畴,flag可能捉襟見肘。
Cobra是一個用來創(chuàng)建強(qiáng)大的現(xiàn)代CLI命令行的Go開源庫突雪。開源包可能比較合適構(gòu)建更為復(fù)雜的命令行程序起惕。開源地址:https://github.com/spf13/cobra
本書《Go語言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42
本書《Go語言四十二章經(jīng)》內(nèi)容在簡書同步地址: http://www.reibang.com/nb/29056963雖然本書中例子都經(jīng)過實際運(yùn)行,但難免出現(xiàn)錯誤和不足之處咏删,煩請您指出惹想;如有建議也歡迎交流。
聯(lián)系郵箱:roteman@163.com