Golang開(kāi)發(fā)命令行之flag包的使用方法
1、命令行工具概述
日常命令行操作,相對(duì)應(yīng)的眾多命令行工具是提高生產(chǎn)力的必備工具,鼠標(biāo)能夠讓用戶更容易上手,降低用戶學(xué)習(xí)成本。 而對(duì)于開(kāi)發(fā)者,鍵盤操作模式能顯著提升生產(chǎn)力,還有在一些專業(yè)工具中, 大量使用快捷鍵代替繁瑣的鼠標(biāo)操作,能夠使開(kāi)發(fā)人員更加專注于工作,提高效率,因?yàn)殒I盤操作模式更容易產(chǎn)生肌肉記憶
舉個(gè)栗子:我司業(yè)務(wù)研發(fā),前些年在我們的強(qiáng)力推動(dòng)下(被迫)轉(zhuǎn)向使用了 git
作為版本控制,開(kāi)始使用的是圖形化“小烏龜”工具。后續(xù)出現(xiàn)幾次問(wèn)題解決起來(lái)較麻煩后,推薦其使用原生的 git 命令行。如今,使用 git 命令行操作版本控制可謂 “一頓操作猛如虎......”
命令行(鍵盤)操作在很大程度上可以提高工作效率,與之相對(duì)應(yīng)的是鼠標(biāo)(觸屏等)操作,這兩種模式是目前的主流人機(jī)交互方式
設(shè)計(jì)一款命令行工具的開(kāi)發(fā)語(yǔ)言可以選擇原始的 shell
、甚至是更原始的語(yǔ)言 C ,更為容易上手且功能更多的有 node
、 python
、 golang
本文是基于 golang
開(kāi)發(fā)命令行工具的開(kāi)篇,主要是基于 golang
原生內(nèi)置的、輕量的 flag
包實(shí)現(xiàn),用 golang
設(shè)計(jì)命令行工具而不用 shell
、 python
的原因這里就不做論述了
2、flag包介紹
flag 包用來(lái)解析命令行參數(shù)
相比簡(jiǎn)單的使用 os.Args
來(lái)獲取命令行參數(shù), flag
可以實(shí)現(xiàn)按照更為通用的命令行用法,例如 mysql -u root -p 123456
。其中 mysql
是命令行的名稱即這個(gè)命令, -u
和 -p
分別是這個(gè)命令的兩個(gè)參數(shù):用戶名和密碼,后面接著的是對(duì)應(yīng)的參數(shù)值,有了參數(shù)的聲明之后,兩個(gè)參數(shù)可以互換位置,參數(shù)值也可以選填或按照缺省(默認(rèn))值進(jìn)行指定
flag
包支持的命令行參數(shù)的類型有 bool
、 int
、 int64
、 uint
、 uint64
、 float float64
、 string
、 duration
即布爾值、整型、浮點(diǎn)型、字符串、時(shí)間段類型
3、flag包命令行參數(shù)的定義
定義 flag
命令行參數(shù),用來(lái)接收命令行輸入的參數(shù)值,一般有以下兩種方法
flag.TypeVar():先定義參數(shù)(實(shí)際上是指針),再定義 flag.TypeVar
將命令行參數(shù)存儲(chǔ)(綁定)到前面參數(shù)的值的指針(地址)
var name string var age int var height float64 var graduated bool // &name 就是接收用戶命令行中輸入的-n后面的參數(shù)值 // 返回值是一個(gè)用來(lái)存儲(chǔ)name參數(shù)的值的指針/地址 // 定義string類型命令行參數(shù)name,括號(hào)中依次是變量名、flag參數(shù)名、默認(rèn)值、參數(shù)說(shuō)明 flag.StringVar(&name, "n", "", "name參數(shù),默認(rèn)為空") // 定義整型命令行參數(shù)age flag.IntVar(&age,"a", 0, "age參數(shù),默認(rèn)為0") // 定義浮點(diǎn)型命令行參數(shù)height flag.Float64Var(&height,"h", 0, "height參數(shù),默認(rèn)為0") // 定義布爾型命令行參數(shù)graduated flag.BoolVar(&graduated,"g", false, "graduated參數(shù),默認(rèn)為false")
flag.Type():
用短變量聲明的方式定義參數(shù)類型及變量名
// 定義string類型命令行參數(shù)name,括號(hào)中依次是flag參數(shù)名、默認(rèn)值、參數(shù)說(shuō)明 namePtr := flag.String("n", "", "name參數(shù),默認(rèn)為空") // 定義整型命令行參數(shù)age age := flag.Int("a", 0, "age參數(shù),默認(rèn)為0") // 定義浮點(diǎn)型命令行參數(shù)height height := flag.Float64("h", 0, "height參數(shù),默認(rèn)為0") // 定義布爾型命令行參數(shù)graduated graduated:= flag.Bool("g", false, "graduated參數(shù),默認(rèn)為false")
4、flag包命令行參數(shù)解析
固定用法,定義好參數(shù)后,通過(guò)調(diào)用 flag.Parse()
來(lái)對(duì)命令行參數(shù)進(jìn)行解析寫入注冊(cè)的 flag
里,進(jìn)而解析獲取參數(shù)值,通過(guò)查看源碼中也是調(diào)用的 os.Args
源碼路徑 go/src/flag/flag.go
// Parse parses the command-line flags from os.Args[1:]. Must be called // after all flags are defined and before flags are accessed by the program. func Parse() { // Ignore errors; CommandLine is set for ExitOnError. CommandLine.Parse(os.Args[1:]) }
進(jìn)而查看 Parse
方法的源碼
func (f *FlagSet) Parse(arguments []string) error { f.parsed = true f.args = arguments for { seen, err := f.parseOne() if seen { continue } if err == nil { break } switch f.errorHandling { case ContinueOnError: return err case ExitOnError: if err == ErrHelp { os.Exit(0) } os.Exit(2) case PanicOnError: panic(err) } } return nil }
真正解析參數(shù)的是 parseOne
方法(這里省略源碼),結(jié)論是
- 當(dāng)遇到單獨(dú)的一個(gè) "-" 或不是 "-" 開(kāi)始時(shí),會(huì)停止解析
- 遇到連續(xù)的兩個(gè) "-" 時(shí),解析停止
- 在終止符"-"之后停止
解析參數(shù)時(shí),對(duì)于參數(shù)的指定方式一般有"-"、"--"、以及是否空格等方式,組合下來(lái)有如下幾種方式
-flag xxx | 空格和一個(gè) - 符號(hào) |
---|---|
--flag xxx | 空格和兩個(gè) - 符號(hào) |
-flag=xxx | 等號(hào)和一個(gè) - 符號(hào) |
--flag=xxx | 等號(hào)和兩個(gè) - 符號(hào) |
其中, -flag xxx
方式最為常用,如果參數(shù)是布爾型,只能用等號(hào)方式指定
5、flag包命令行幫助
flag
包默認(rèn)會(huì)根據(jù)定義的命令行參數(shù),在使用時(shí)如果不輸入?yún)?shù)就打印對(duì)應(yīng)的幫助信息
這樣的幫助信息我們可以對(duì)其進(jìn)行覆蓋去改變默認(rèn)的 Usage
package main import ( "flag" "fmt" ) func main() { var host string var port int var verbor bool var help bool // 綁定命令行參數(shù)與變量關(guān)系 flag.StringVar(&host, "H", "127.0.0.1", "ssh host") flag.IntVar(&port, "P", 22, "ssh port") flag.BoolVar(&verbor, "v", false, "detail log") flag.BoolVar(&help, "h", false, "help") // 自定義-h flag.Usage = func() { fmt.Println(` Usage: flag [-H addr] [-p port] [-v] Options: `) flag.PrintDefaults() } // 解析命令行參數(shù) flag.Parse() if help { flag.Usage() } else { fmt.Println(host, port, verbor) } } /* ➜ go run flag_args.go -h Usage: flag [-H addr] [-p port] [-v] Options: -H string ssh host (default "127.0.0.1") -P int ssh port (default 22) -h help -v detail log */
6、flag定義短參數(shù)和長(zhǎng)參數(shù)
簡(jiǎn)單來(lái)說(shuō),短參數(shù)和長(zhǎng)參數(shù),就是例如我們?cè)谑褂媚承┟顣r(shí),查看命令版本可以輸入 -V
,也可以輸入 --version
。這種情況下, flag
并沒(méi)有默認(rèn)支持,但是可以通過(guò)可以兩個(gè)選項(xiàng)共享同一個(gè)變量來(lái)實(shí)現(xiàn),即通過(guò)給某個(gè)相同的變量設(shè)置不同的選項(xiàng),參數(shù)在初始化的時(shí)候其順序是不固定的,因此還需要保證其擁有相同的默認(rèn)值
package main import ( "fmt" "flag" ) var logLevel string func init() { const ( defaultLogLevel = "DEBUG" usage = "set log level" ) flag.StringVar(&logLevel, "log_level", defaultLogLevel, usage) flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(shorthand)") } func main() { flag.Parse() fmt.Println("log level:", logLevel) }
通過(guò) const
聲明公共的常量,并在默認(rèn)值以及幫助信息中去使用,這樣就可以實(shí)現(xiàn)了
7、示例
實(shí)現(xiàn)計(jì)算字符串或目錄下遞歸計(jì)算文件 md5
的命令,類似 linux
的 md5sum
命令
其中利用 bufio
分批次讀取文件,防止文件過(guò)大時(shí)造成資源占用高
package main import ( "bufio" "crypto/md5" "flag" "fmt" "io" "os" "strings" ) func md5reader(reader *bufio.Reader) string { // hasher := md5.New() // 定義MD5 hash計(jì)算器 bytes := make([]byte, 1024*1024*10) // 分批次讀取文件 for { n, err := reader.Read(bytes) if err != nil { if err != io.EOF { return "" } break } else { hasher.Write(bytes[:n]) } } return fmt.Sprintf("%x", hasher.Sum(nil)) } func md5file(path string) (string, error) { file, err := os.Open(path) if err != nil { return "", err } else { defer file.Close() return md5reader(bufio.NewReader(file)), nil } } func md5str(txt string) (string, error) { return md5reader(bufio.NewReader(strings.NewReader(txt))), nil //return fmt.Sprintf("%x", md5.Sum([]byte(txt))) } func main() { txt := flag.String("s", "", "md5 txt") path := flag.String("f", "", "file path") help := flag.Bool("h", false, "help") flag.Usage = func() { fmt.Println(` Usage: md5 [-s 123abc] [-f path] Options: `) flag.PrintDefaults() } flag.Parse() if *help || *txt == "" && *path == "" { flag.Usage() } else { var md5 string var err error if *path != "" { md5, err = md5file(*path) } else { md5, err = md5str(*txt) } if err != nil { fmt.Println(err) } else { fmt.Println(md5) } } }
編譯生成二進(jìn)制文件
➜ go build -o md5go -x md5_bufio.go ➜ ll md5go -rwxr-xr-x 1 ssgeek staff 1.9M Oct 2 00:54 md5go
測(cè)試使用
➜ ./md5go -h Usage: md5 [-s 123abc] [-f path] Options: -f string file path -h help -s string md5 txt ➜ ./md5go -s 123456 e10adc3949ba59abbe56e057f20f883e ➜ ./md5go -f md5_bufio.go 8607a07cbb98cec0e9abe14b0db0bee6
到此這篇關(guān)于Golang開(kāi)發(fā)命令行之flag包的使用方法的文章就介紹到這了,更多相關(guān)Golang開(kāi)發(fā)命令行之flag包的使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang時(shí)間/時(shí)間戳的獲取與轉(zhuǎn)換實(shí)例代碼
說(shuō)實(shí)話,golang的時(shí)間轉(zhuǎn)化還是很麻煩的,最起碼比php麻煩很多,下面這篇文章主要給大家介紹了關(guān)于golang時(shí)間/時(shí)間戳的獲取與轉(zhuǎn)換的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11Golang學(xué)習(xí)筆記(一):簡(jiǎn)介
這篇文章主要介紹了Golang學(xué)習(xí)筆記(一):簡(jiǎn)介,本文講解了Go語(yǔ)言最主要的特性、安裝、環(huán)境變量設(shè)置、整體目錄結(jié)構(gòu)、Helloworld、go命令、調(diào)試、編輯器設(shè)置等內(nèi)容,需要的朋友可以參考下2015-05-05深入探索Go語(yǔ)言中的高效數(shù)據(jù)結(jié)構(gòu)堆
堆,作為一種基本的數(shù)據(jù)結(jié)構(gòu),以其在優(yōu)先隊(duì)列和排序算法中提供高效解決方案的能力而聞名。在本文中,我們將深入探討堆的內(nèi)部工作原理,包括其特性、實(shí)現(xiàn)細(xì)節(jié)以及在現(xiàn)代編程中的應(yīng)用2008-06-06Go語(yǔ)言Gin框架獲取請(qǐng)求參數(shù)的兩種方式
在添加路由處理函數(shù)之后,就可以在路由處理函數(shù)中編寫業(yè)務(wù)處理代碼了,而編寫業(yè)務(wù)代碼第一件事一般就是獲取HTTP請(qǐng)求的參數(shù)吧,Gin框架在net/http包的基礎(chǔ)上封裝了獲取參數(shù)的方式,本文小編給大家介紹了獲取參數(shù)的兩種方式,需要的朋友可以參考下2024-01-01