網站首頁 編程語言 正文
前言
Golang 標準庫中的 flag 庫提供了解析命令行選項的能力,我們可以基于此來開發命令行工具。
假設我們想做一個命令行工具,我們通過參數提供【城市】,它自動能夠返回當前這個【城市】的天氣狀況。這樣一個簡單的需求,今天我們就來試一下,看怎樣實現。
flag 庫
Package flag implements command-line flag parsing.
flag 庫?能夠支持基礎的命令行 flag 解析。使用起來并不復雜,
我們可以針對 string, integer, bool 三種類型來定義 flag,如:flag.String(), Bool(), Int()。
比如下面這樣,我們就定義了一個?-n
?的選項,默認值為 1234, 提示信息為?help message for flag n
。返回值是一個 int 的指針。
import "flag" var nFlag = flag.Int("n", 1234, "help message for flag n")
當然,我們也可以直接將 flag 和變量綁定,這里要在上面三種方法的前面加上 Var 即可:
var flagvar int func init() { flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") }
區別只在于首個參數是個指針,直接賦值,而不是 return 回來。簽名都是類似的,我們看一個 Int64Var:
在所有 flag 都定義好之后,我們調用?flag.Parse()
?方法,將命令行數據解析到對應的 flag 中。這之后就可以直接用了:
fmt.Println("ip has value ", *ip) fmt.Println("flagvar has value ", flagvar)
有時候我們不止是一個簡單的 flag,還需要參數,這個時候我們就可以用?flag.Args()
?拿到一個 slice,或者直接?flag.Arg(i)
?來拿指定參數,下標從 0 開始。
不熟悉的同學建議多看看看?go by example?的示例,講的很清楚。
從開發者的角度看,其實我們只要定義好變量,用 flag.XXVar 來綁定,最后 flag.Parse 就可以用:
package main import ( "fmt" "flag" ) var ( intflag int boolflag bool stringflag string ) func init() { flag.IntVar(&intflag, "intflag", 0, "int flag value") flag.BoolVar(&boolflag, "boolflag", false, "bool flag value") flag.StringVar(&stringflag, "stringflag", "default", "string flag value") } func main() { flag.Parse() fmt.Println("int flag:", intflag) fmt.Println("bool flag:", boolflag) fmt.Println("string flag:", stringflag) }
編譯之后我們運行一下看看
$ ./main -intflag 12 -boolflag 1 -stringflag test int flag: 12 bool flag: true string flag: test
如果沒有設置 flag 的值,會取我們設置的默認值。
flag 支持的解析類型有下面四種:
- -flag
- --flag
- -flag=x
- -flag x (bool 不能用這個)
有時候我們只需要一個 flag 就夠了,選項本身就帶著含義,不需要參數。而有些時候我們既需要 flag,也需要參數。注意區分好場景即可。如果用了第一種和第二種這種不帶參數的,本質含義就是個 bool,出現就是 true,不出現就看默認值。
FlagSet
FlagSet 就是一組 flag 定義的集合,在 flag 庫的底層是一個結構體:
type FlagSet struct { // Usage is the function called when an error occurs while parsing flags. // The field is a function (not a method) that may be changed to point to // a custom error handler. What happens after Usage is called depends // on the ErrorHandling setting; for the command line, this defaults // to ExitOnError, which exits the program after calling Usage. Usage func() // contains filtered or unexported fields }
注意有一個 Usage 函數,當解析 flag 出問題時就會調用這個。前面 flag 庫封裝的那些能力底層都是共用同一個默認的 CommandLine FlagSet實現的:
// src/flag/flag.go var CommandLine = NewFlagSet(os.Args[0], ExitOnError) func Parse() { CommandLine.Parse(os.Args[1:]) } func IntVar(p *int, name string, value int, usage string) { CommandLine.Var(newIntValue(value, p), name, usage) } func Int(name string, value int, usage string) *int { return CommandLine.Int(name, value, usage) } func NFlag() int { return len(CommandLine.actual) } func Arg(i int) string { return CommandLine.Arg(i) } func NArg() int { return len(CommandLine.args) }
當我們調用 NewFlagSet 時需要指定這個集合的名稱以及對應的錯誤處理。
第二個參數這個錯誤處理有三種選項,flag 已經提供:
-
ContinueOnError
:發生錯誤后繼續解析,CommandLine
就是使用這個選項; -
ExitOnError
:出錯時調用os.Exit(2)
退出程序; -
PanicOnError
:出錯時產生 panic。
需求拆解
我們的需求很簡單,提供一個 weather flag,接受輸入的城市名稱,隨后我們返回天氣數據即可。
所以,從 flag 的角度看,這里并不復雜,我們將【城市名稱】綁定到一個 flag 上即可。
關鍵是要實現基于城市名稱查天氣的能力。這個也有公開的網站能實現,參照此前 Golang 教程中給出的 wttr 就可以。大家可以試一下,訪問?wttr.in/wuhan?將會展示【武漢】的天氣預報:
這里其實比較 trick,由于是網站,并不是公開的 open api,所以返回的數據也是 html 格式的,我們要思考一下怎么在命令行展示。
下面我們一步步來解決。
實現 weather flag
這一步基本是復用 flag 包提供的能力,這里我們用 StringVar 從命令行拿到值之后寫入變量,這里相對比較通用,大家以后有需求可以直接改一下即可:
package main import ( "flag" "fmt" "os" ) type arguments struct { weatherCity string } func (a *arguments) parseArgs(args []string) error { f := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) f.StringVar(&a.weatherCity, "weather", "", "check weather") f.Usage = func() { fmt.Fprintf(os.Stderr, `flags: %s`, os.Args[0]) f.PrintDefaults() os.Exit(1) } if err := f.Parse(args[1:]); err != nil { return err } return nil } func Execute() error { args := &arguments{} if err := args.parseArgs(os.Args); err != nil { fmt.Println(err) os.Exit(2) } // weather if args.weatherCity != "" { // TODO 實現根據 city 名稱拿天氣,并打印的效果 } return nil }
最終在 main() 函數中直接調用我們的 Execute 即可,注意我們解析到 weatherCity 不為空時,核心邏輯就在這個分支,我們留了個 TODO,下面看看怎么解。
天氣數據打印
前一節的 TODO 里本質需要我們實現的簽名很簡單:
func GetWeather(city string) (string, error)
這樣拿到一個用字符串表示的天氣數據,然后回到主流程里一個?fmt.Printf
?就可以解決。
而同時我們也有了 wttr 的能力,可以拿到數據,只不過是 html。關鍵是怎么轉字符串。我們一步一步來:
獲取源數據
一個簡單的 http.Get 拿到 html 數據即可,這一步不復雜,大家直接看代碼:
func getWeatherData(city string) string { url := "https://wttr.in/" + city response, err := http.Get(url) if err != nil { return "", err } all, err := ioutil.ReadAll(response.Body) if err != nil { return "", err } weather := string(all) return weather }
數據轉換
在開源社區,我們找到了?"github.com/JohannesKaufmann/html-to-markdown"
?這個庫提供 html 轉換為 markdown 的能力,而?"github.com/MichaelMure/go-term-markdown"
?又可以將 markdown 格式轉為可在 terminal 打印的字符串,我們可以通過這兩步來轉換,實現整體的 GetWeather 函數:
package weather import ( "io/ioutil" "net/http" md "github.com/JohannesKaufmann/html-to-markdown" markdown "github.com/MichaelMure/go-term-markdown" ) func GetWeather(city string) (string, error) { url := "https://wttr.in/" + city response, err := http.Get(url) if err != nil { return "", err } all, err := ioutil.ReadAll(response.Body) if err != nil { return "", err } weather := string(all) md := getMD(weather) result := markdown.Render(md, 280, 6) return string(result), nil } func getMD(html string) string { converter := md.NewConverter("", true, nil) markdown, err := converter.ConvertString(html) if err != nil { return "" } return markdown }
運行效果
好了,現在我們實現了兩步,大家只需要把主流程里 TODO 的注釋換成實際對下面 GetWeather 函數的調用即可,我們來看看運行效果。
$ opb -weather beijing
完美,一個展示天氣狀況的命令行工具就做完了。這里的 opb 是我們的 package 名稱,大家可以自己試一下,包名更換為你喜歡的名稱即可。
小結
其實開源社區各種能力基本都有同學研究過,大家可以打開思路,碰到知識點就思考如何能落地。筆者也是初學 flag 的時候本著實踐的目的來試一試。正好發現了 html => markdown => terminal 打印這條路徑,不一定是最好的,但作為一個 toy tool 足夠了。
原文鏈接:https://juejin.cn/post/7137669791879266335
相關推薦
- 2022-09-13 C#?wpf使用ListBox實現尺子控件的示例代碼_C#教程
- 2023-01-02 Android?數據結構全面總結分析_Android
- 2022-04-20 python日志模塊loguru詳解_python
- 2022-03-14 Failed to load ApplicationContext異常的解決思路
- 2022-07-12 for循環中var和let的不為人知的秘密
- 2022-03-14 ffmpeg開發打印音視頻meta信息
- 2022-10-30 Android中二維碼的掃描和生成(使用zxing庫)_Android
- 2022-12-11 centos離線安裝mongodb-database-tools方法詳解_MongoDB
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支