alexflint/go-arg

GitHub: alexflint/go-arg

基于 struct tag 的 Go 命令行参数解析库,通过结构体字段声明即可自动完成参数解析、帮助文本生成和环境变量支持。

Stars: 2269 | Forks: 114

go-arg
go-arg

Struct-based argument parsing for Go

Sourcegraph Documentation Build Status Coverage Status Go Report Card


通过定义一个 struct 来为你的程序声明命令行参数。 ``` var args struct { Foo string Bar bool } arg.MustParse(&args) fmt.Println(args.Foo, args.Bar) ``` ``` $ ./example --foo=hello --bar hello true ``` ### 安装说明 ``` go get github.com/alexflint/go-arg ``` ### 必需参数 ``` var args struct { ID int `arg:"required"` Timeout time.Duration } arg.MustParse(&args) ``` ``` $ ./example Usage: example --id ID [--timeout TIMEOUT] error: --id is required ``` ### 位置参数 ``` var args struct { Input string `arg:"positional"` Output []string `arg:"positional"` } arg.MustParse(&args) fmt.Println("Input:", args.Input) fmt.Println("Output:", args.Output) ``` ``` $ ./example src.txt x.out y.out z.out Input: src.txt Output: [x.out y.out z.out] ``` ### 环境变量 ``` var args struct { Workers int `arg:"env"` } arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` ``` $ WORKERS=4 ./example Workers: 4 ``` ``` $ WORKERS=4 ./example --workers=6 Workers: 6 ``` 你还可以覆盖环境变量的名称: ``` var args struct { Workers int `arg:"env:NUM_WORKERS"` } arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` ``` $ NUM_WORKERS=4 ./example Workers: 4 ``` 你可以使用逗号在环境变量中提供多个值: ``` var args struct { Workers []int `arg:"env"` } arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` ``` $ WORKERS='1,99' ./example Workers: [1 99] ``` 命令行参数的优先级高于环境变量: ``` var args struct { Workers int `arg:"--count,env:NUM_WORKERS"` } arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` ``` $ NUM_WORKERS=6 ./example Workers: 6 $ NUM_WORKERS=6 ./example --count 4 Workers: 4 ``` 也可以配置全局环境变量名称前缀: ``` var args struct { Workers int `arg:"--count,env:NUM_WORKERS"` } p, err := arg.NewParser(arg.Config{ EnvPrefix: "MYAPP_", }, &args) p.MustParse(os.Args[1:]) fmt.Println("Workers:", args.Workers) ``` ``` $ MYAPP_NUM_WORKERS=6 ./example Workers: 6 ``` ### 用法字符串 ``` var args struct { Input string `arg:"positional"` Output []string `arg:"positional"` Verbose bool `arg:"-v,--verbose" help:"verbosity level"` Dataset string `help:"dataset to use"` Optimize int `arg:"-O" help:"optimization level"` } arg.MustParse(&args) ``` ``` $ ./example -h Usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]] Positional arguments: INPUT OUTPUT Options: --verbose, -v verbosity level --dataset DATASET dataset to use --optimize OPTIMIZE, -O OPTIMIZE optimization level --help, -h print this help message ``` ### 默认值 ``` var args struct { Foo string `default:"abc"` Bar bool } arg.MustParse(&args) ``` 命令行参数的优先级高于环境变量,而环境变量的优先级高于默认值。这意味着我们会先检查命令行中是否提供了某个选项,如果没有,则检查是否有对应的环境变量(仅在提供了 `env` tag 的情况下),如果都没有找到,最后才会检查包含默认值的 `default` tag。 ``` var args struct { Test string `arg:"-t,env:TEST" default:"something"` } arg.MustParse(&args) ``` #### 忽略环境变量和/或默认值 ``` var args struct { Test string `arg:"-t,env:TEST" default:"something"` } p, err := arg.NewParser(arg.Config{ IgnoreEnv: true, IgnoreDefault: true, }, &args) err = p.Parse(os.Args[1:]) ``` ### 具有多个值的参数 ``` var args struct { Database string IDs []int64 } arg.MustParse(&args) fmt.Printf("Fetching the following IDs from %s: %q", args.Database, args.IDs) ``` ``` ./example -database foo -ids 1 2 3 Fetching the following IDs from foo: [1 2 3] ``` ### 可多次指定、并与位置参数混合使用的参数 ``` var args struct { Commands []string `arg:"-c,separate"` Files []string `arg:"-f,separate"` Databases []string `arg:"positional"` } arg.MustParse(&args) ``` ``` ./example -c cmd1 db1 -f file1 db2 -c cmd2 -f file2 -f file3 db3 -c cmd3 Commands: [cmd1 cmd2 cmd3] Files [file1 file2 file3] Databases [db1 db2 db3] ``` ### 带有键和值的参数 ``` var args struct { UserIDs map[string]int } arg.MustParse(&args) fmt.Println(args.UserIDs) ``` ``` ./example --userids john=123 mary=456 map[john:123 mary:456] ``` ### 版本字符串 ``` type args struct { ... } func (args) Version() string { return "someprogram 4.3.0" } func main() { var args args arg.MustParse(&args) } ``` ``` $ ./example --version someprogram 4.3.0 ``` ### 自定义验证 ``` var args struct { Foo string Bar string } p := arg.MustParse(&args) if args.Foo == "" && args.Bar == "" { p.Fail("you must provide either --foo or --bar") } ``` ``` ./example Usage: samples [--foo FOO] [--bar BAR] error: you must provide either --foo or --bar ``` ### 覆盖选项名称 ``` var args struct { Short string `arg:"-s"` Long string `arg:"--custom-long-option"` ShortAndLong string `arg:"-x,--my-option"` OnlyShort string `arg:"-o,--"` } arg.MustParse(&args) ``` ``` $ ./example --help Usage: example [-o ONLYSHORT] [--short SHORT] [--custom-long-option CUSTOM-LONG-OPTION] [--my-option MY-OPTION] Options: --short SHORT, -s SHORT --custom-long-option CUSTOM-LONG-OPTION --my-option MY-OPTION, -x MY-OPTION -o ONLYSHORT --help, -h display this help and exit ``` ### 嵌入式 struct 嵌入式 struct 的字段与常规字段的处理方式完全相同: ``` type DatabaseOptions struct { Host string Username string Password string } type LogOptions struct { LogFile string Verbose bool } func main() { var args struct { DatabaseOptions LogOptions } arg.MustParse(&args) } ``` 像往常一样,任何带有 `arg:"-"` tag 的字段都会被忽略。 ### 支持的类型 以下类型可用作参数: - 内置整数类型:`int, int8, int16, int32, int64, byte, rune` - 内置浮点数类型:`float32, float64` - 字符串 - 布尔值 - 表示为 `url.URL` 的 URL - 表示为 `time.Duration` 的持续时间 - 表示为 `mail.Address` 的电子邮件地址 - 表示为 `net.HardwareAddr` 的 MAC 地址 - 指向上述任何类型的指针 - 上述任何类型的切片 - 使用上述任何类型作为键和值的 map - 任何实现了 `encoding.TextUnmarshaler` 的类型 ### 自定义解析 实现 `encoding.TextUnmarshaler` 可以定义你自己的解析逻辑。 ``` // Accepts command line arguments of the form "head.tail" type NameDotName struct { Head, Tail string } func (n *NameDotName) UnmarshalText(b []byte) error { s := string(b) pos := strings.Index(s, ".") if pos == -1 { return fmt.Errorf("missing period in %s", s) } n.Head = s[:pos] n.Tail = s[pos+1:] return nil } func main() { var args struct { Name NameDotName } arg.MustParse(&args) fmt.Printf("%#v\n", args.Name) } ``` ``` $ ./example --name=foo.bar main.NameDotName{Head:"foo", Tail:"bar"} $ ./example --name=oops Usage: example [--name NAME] error: error processing --name: missing period in "oops" ``` ### 带有默认值的自定义解析 实现 `encoding.TextMarshaler` 可以定义你自己的默认值字符串: ``` // Accepts command line arguments of the form "head.tail" type NameDotName struct { Head, Tail string } func (n *NameDotName) UnmarshalText(b []byte) error { // same as previous example } // this is only needed if you want to display a default value in the usage string func (n *NameDotName) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail)), nil } func main() { var args struct { Name NameDotName `default:"file.txt"` } arg.MustParse(&args) fmt.Printf("%#v\n", args.Name) } ``` ``` $ ./example --help Usage: test [--name NAME] Options: --name NAME [default: file.txt] --help, -h display this help and exit $ ./example main.NameDotName{Head:"file", Tail:"txt"} ``` ### 自定义占位符 使用 `placeholder` tag 可以控制在帮助文本中使用的占位符文本。 ``` var args struct { Input string `arg:"positional" placeholder:"SRC"` Output []string `arg:"positional" placeholder:"DST"` Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"` MaxJobs int `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"` } arg.MustParse(&args) ``` ``` $ ./example -h Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]] Positional arguments: SRC DST Options: --optimize LEVEL, -O LEVEL optimization level --maxjobs N, -j N maximum number of simultaneous jobs --help, -h display this help and exit ``` ### 描述字符串 通过实现返回字符串的 `Description` 函数,可以在帮助文本的顶部添加描述信息。 ``` type args struct { Foo string } func (args) Description() string { return "this program does this and that" } func main() { var args args arg.MustParse(&args) } ``` ``` $ ./example -h this program does this and that Usage: example [--foo FOO] Options: --foo FOO --help, -h display this help and exit ``` 类似地,通过实现 `Epilogue` 函数,可以在帮助文本的末尾添加结语。 ``` type args struct { Foo string } func (args) Epilogue() string { return "For more information visit github.com/alexflint/go-arg" } func main() { var args args arg.MustParse(&args) } ``` ``` $ ./example -h Usage: example [--foo FOO] Options: --foo FOO --help, -h display this help and exit For more information visit github.com/alexflint/go-arg ``` ### 子命令 子命令通常用于那些希望将多种功能组合到单个程序中的工具。`git` 工具就是一个例子: ``` $ git checkout [arguments specific to checking out code] $ git commit [arguments specific to committing] $ git push [arguments specific to pushing] ``` 字符串 "checkout"、"commit" 和 "push" 与简单的位置参数不同,因为用户可用的选项会根据他们选择的子命令而改变。 使用 `go-arg` 可以按如下方式实现: ``` type CheckoutCmd struct { Branch string `arg:"positional"` Track bool `arg:"-t"` } type CommitCmd struct { All bool `arg:"-a"` Message string `arg:"-m"` } type PushCmd struct { Remote string `arg:"positional"` Branch string `arg:"positional"` SetUpstream bool `arg:"-u"` } var args struct { Checkout *CheckoutCmd `arg:"subcommand:checkout"` Commit *CommitCmd `arg:"subcommand:commit"` Push *PushCmd `arg:"subcommand:push"` Quiet bool `arg:"-q"` // this flag is global to all subcommands } arg.MustParse(&args) switch { case args.Checkout != nil: fmt.Printf("checkout requested for branch %s\n", args.Checkout.Branch) case args.Commit != nil: fmt.Printf("commit requested with message \"%s\"\n", args.Commit.Message) case args.Push != nil: fmt.Printf("push requested from %s to %s\n", args.Push.Branch, args.Push.Remote) } ``` 在使用子命令时,适用一些额外的规则: * `subcommand` tag 只能用于指向 struct 的指针类型的字段 * 任何包含子命令的 struct 都不得包含任何位置参数 这个 package 允许程序在未指定子命令时执行其他操作,同时也接受子命令。 如果你希望在未指定子命令时终止程序,推荐的方法是: ``` p := arg.MustParse(&args) if p.Subcommand() == nil { p.Fail("missing subcommand") } ``` ### 自定义处理 --help 和 --version 下面复现了 `MustParse` 的内部逻辑,用于未使用子命令或 --version 的简单情况。这允许你以编程方式响应 --help 以及出现的任何错误。 ``` var args struct { Something string } p, err := arg.NewParser(arg.Config{}, &args) if err != nil { log.Fatalf("there was an error in the definition of the Go struct: %v", err) } err = p.Parse(os.Args[1:]) switch { case err == arg.ErrHelp: // indicates that user wrote "--help" on command line p.WriteHelp(os.Stdout) os.Exit(0) case err != nil: fmt.Printf("error: %v\n", err) p.WriteUsage(os.Stdout) os.Exit(1) } ``` ``` $ go run ./example --help Usage: ./example --something SOMETHING Options: --something SOMETHING --help, -h display this help and exit $ ./example --wrong error: unknown argument --wrong Usage: ./example --something SOMETHING $ ./example error: --something is required Usage: ./example --something SOMETHING ``` 要以编程方式同时处理 --version,请使用以下代码: ``` type args struct { Something string } func (args) Version() string { return "1.2.3" } func main() { var args args p, err := arg.NewParser(arg.Config{}, &args) if err != nil { log.Fatalf("there was an error in the definition of the Go struct: %v", err) } err = p.Parse(os.Args[1:]) switch { case err == arg.ErrHelp: // found "--help" on command line p.WriteHelp(os.Stdout) os.Exit(0) case err == arg.ErrVersion: // found "--version" on command line fmt.Println(args.Version()) os.Exit(0) case err != nil: fmt.Printf("error: %v\n", err) p.WriteUsage(os.Stdout) os.Exit(1) } fmt.Printf("got %q\n", args.Something) } ``` ``` $ ./example --version 1.2.3 $ go run ./example --help 1.2.3 Usage: example --something SOMETHING Options: --something SOMETHING --help, -h display this help and exit $ ./example --wrong 1.2.3 error: unknown argument --wrong Usage: example --something SOMETHING $ ./example error: --something is required Usage: example --something SOMETHING ``` 要生成特定于子命令的帮助消息,请使用以下最通用的版本(这在没有子命令的情况下也能工作,但稍微复杂一些): ``` type fetchCmd struct { Count int } type args struct { Something string Fetch *fetchCmd `arg:"subcommand"` } func (args) Version() string { return "1.2.3" } func main() { var args args p, err := arg.NewParser(arg.Config{}, &args) if err != nil { log.Fatalf("there was an error in the definition of the Go struct: %v", err) } err = p.Parse(os.Args[1:]) switch { case err == arg.ErrHelp: // found "--help" on command line p.WriteHelpForSubcommand(os.Stdout, p.SubcommandNames()...) os.Exit(0) case err == arg.ErrVersion: // found "--version" on command line fmt.Println(args.Version()) os.Exit(0) case err != nil: fmt.Printf("error: %v\n", err) p.WriteUsageForSubcommand(os.Stdout, p.SubcommandNames()...) os.Exit(1) } } ``` ``` $ ./example --version 1.2.3 $ ./example --help 1.2.3 Usage: example [--something SOMETHING] [] Options: --something SOMETHING --help, -h display this help and exit --version display version and exit Commands: fetch $ ./example fetch --help 1.2.3 Usage: example fetch [--count COUNT] Options: --count COUNT Global options: --something SOMETHING --help, -h display this help and exit --version display version and exit ``` ### API 文档 https://pkg.go.dev/github.com/alexflint/go-arg ### 设计初衷 Go 有很多命令行参数解析库,包括标准库中的一个,那为什么还要再写一个呢? 对我来说,标准库中自带的 `flag` 库显得很笨拙。位置参数必须放在选项前面,因此 `./prog x --foo=1` 会按你预期的那样工作,但 `./prog --foo=1 x` 却不行。它也不允许参数同时具有长格式(`--foo`)和短格式(`-f`)。 许多第三方的参数解析库非常适合编写复杂的命令行界面,但对于一个只有几个 flag 的简单脚本来说,我觉得有些大材小用了。 `go-arg` 背后的理念是,Go 已经有一种使用 struct 来描述数据结构的绝佳方式,因此没有必要开发额外的抽象层。其他库需要一个 API 来指定程序接受哪些参数,然后再用另一个 API 来获取这些参数的值,而 `go-arg` 用一个单一的 struct 就取代了这两者。 ### 向后兼容性说明 该库的早期版本要求将帮助文本作为 `arg` tag 的一部分。目前仍然支持这种做法,但已不再推荐。相反,你应该使用单独的 `help` tag,如上所述,这使得在帮助文本中包含逗号成为可能。
标签:EVTX分析, Go, Ruby工具, 命令行解析, 开发工具库, 日志审计