flag
是Go语言标准库中提供的一个命令行参数解析框架,我们开发控制台程序时可以用flag
包实现控制台参数解析、子命令解析、帮助信息生成等功能,这篇笔记我们介绍flag
包的使用。
这里我们直接通过一个简单例子的形式演示flag
包的使用。下面例子代码接收name
、age
、married
共3个控制台参数,其中前2个为必传,第3个参数默认值为false
。
package main
import (
"flag"
"fmt"
)
func main() {
// 定义命令行参数
name := flag.String("name", "", "Your name")
age := flag.Int("age", 0, "Your age")
married := flag.Bool("married", false, "Married or not")
// 解析命令行参数
flag.Parse()
// 参数校验
if *name == "" {
fmt.Println("name is empty")
return
}
if *age == 0 {
fmt.Println("age is empty")
return
}
// 打印获取的参数
fmt.Printf("name: %s, age: %d, married: %t\n", *name, *age, *married)
}
flag
中我们可以使用flag.String()
、flag.Int()
、flag.Bool()
等函数定义不同数据类型的命令行参数,这些具名的参数被称为Flag参数(标志参数),这些函数的返回值是读取到的数据的指针,定义完变量后,我们需要调用flag.Parse()
函数执行参数解析,此时我们就可以拿到变量的值了。如果某个参数是必传的,我们可以随后对变量的值进行判断,对于必传参数如果变量的值此时仍是默认值(意味着必传参数没有传递)就可以打印报错信息并终止程序。
编译程序后,我们可以用如下形式指定命令行参数,并观察程序的输出结果。
demoflag -name Lucy -age 18 -married
对于字符串和整数参数,我们需要在对应参数名后加上参数值,而布尔类型参数则是指定就意味着被设置为了true
。除了-name Lucy
这种写法,我们也可以使用--name Lucy
、-name=Lucy
、--name=Lucy
这些传递参数的写法,它们的效果和-name Lucy
是完全一样的。对于布尔类型,我们也可以指定-married=false
来指定将参数值设置为false
。
此外,代码中我们还设置了很多提示信息,例如“Your name”、“Your age”等,这些信息用于flag
包输出提示信息,这些信息的打印可以通过-h
或--help
触发,提示信息的输出例子如下。
Usage of /home/ubuntu/demoflag:
-age int
Your age
-married
Married or not
-name string
Your name
在某些情况下,我们也可以直接调用flag.Usage()
函数触发这些信息的打印。
有些时候自动生成的帮助信息不符合我们的要求,我们可以将自定义的函数设置为Usage()
,这样就可以实现自定义帮助信息。
flag.Usage = func() {
fmt.Println("Usage of demoflag is very simple, I won't explain it.")
}
除了Flag参数,其余的参数被称为位置参数,我们可以使用flag.Args()
函数获取位置参数数组,下面是一个例子。
package main
import (
"flag"
"fmt"
)
func main() {
// 定义命令行参数
name := flag.String("name", "", "Your name")
age := flag.Int("age", 0, "Your age")
married := flag.Bool("married", false, "Married or not")
// 解析命令行参数
flag.Parse()
// 参数校验
if *name == "" {
fmt.Println("name is empty")
return
}
if *age == 0 {
fmt.Println("age is empty")
return
}
// 打印获取的参数
fmt.Printf("name: %s, age: %d, married: %t\n", *name, *age, *married)
// 获取位置参数并打印
args := flag.Args()
for _, arg := range args {
fmt.Println(arg)
}
}
一些比较复杂的命令行工具通常包含多个子命令,例如go
命令行工具就包含go mod
、go build
、go get
等多个子命令,flag
包也支持子命令的设置,下面是一个例子。
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// 获取子命令
if len(os.Args) < 2 {
fmt.Println("Invalid subcommand")
return
}
subCmd := os.Args[1]
// 设置update子命令
updateCmd := flag.NewFlagSet("update", flag.ExitOnError)
updateUrl := updateCmd.String("url", "", "URL")
// 设置add子命令
addCmd := flag.NewFlagSet("add", flag.ExitOnError)
addName := addCmd.String("name", "", "Your name")
addAge := addCmd.Int("age", 0, "Your age")
addMarried := addCmd.Bool("married", false, "Married or not")
// 解析命令行参数
flag.Parse()
// 根据参数输出不同信息
switch subCmd {
case "update":
fmt.Printf("Updating from %s ...\n", *updateUrl)
case "add":
fmt.Printf("name: %s, age: %d, married: %t\n", *addName, *addAge, *addMarried)
default:
fmt.Println("Invalid subcommand")
}
}
代码中我们创建了两个FlagSet
子命令,我们使用switch...case
语句来判断用户使用哪个子命令并输出不同信息。此外,子命令的FlagSet
也可以通过设置Usage()
函数的方式来定制帮助信息。