另一个go命令行参数处理器 - cmdr [mod]
cmdr 是另一个命令行参数处理器。
Golang 自己带有 在计算机人机交互界面的历史上,命令行的交互方式只有一种是贯穿始终,得到传承和延续的,那就是 所以,自行其是,自己搞一套,并非不可以。但我可以不买账。 那么,这并非我独自一人的自赏。我们只需要知道,在 Golang 的开源圈子里,已经有了数十种 getopt-like 的复刻本,用以为 Golang 开发的应用程序提供更好的命令行界面。这里面不乏 viper/cobra,cli 那样的巨作,也有一些小巧精干的实现。
让我们来看看都有哪些具体方面。 POSIX 约定POSIX 表示可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)是 IEEE(电气和电子工程师协会,Institute of Electrical and Electronics Engineers)为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为ISO/IEC 9945。此标准源于一个大约开始于1985年的项目。POSIX这个名称是由 理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写,而X则表明其对Unix API的传承。 电气和电子工程师协会(Institute of Electrical and Electronics Engineers,IEEE)最初开发 POSIX 标准,是为了提高 UNIX 环境下应用程序的可移植性。然而,POSIX 并不局限于 UNIX。许多其它的操作系统,例如 DEC OpenVMS 和 Microsoft Windows NT,都支持 POSIX 标准。 下面是 POSIX 标准中关于程序名、参数的约定:
GNU长选项约定
getopt 界面以下对 getopt 以及 getopt_long 提供的界面进行描述, 在以下的行文中, 短参数单个短横线引导的单个字符的参数,被称为短参数。例如: ep Exclude paths from names ep1 Exclude base directory from names ep3 Expand paths to full including the drive letter 然而在实现其处理器时,我们可以提供 长参数两个短横线引导的多个字符的参数,被称为长参数。例如: 一般来说,长参数更具备描述性,通常使用单词、词组来构成长参数。例如 docker 的子命令 $ docker checkpoint create --help Usage: docker checkpoint create [OPTIONS] CONTAINER CHECKPOINT Create a checkpoint from a running container Options: --checkpoint-dir string Use a custom checkpoint storage directory --leave-running Leave the container running after checkpoint 参数描述每条命令或参数选项可以被一段文件以描述。 参数重复堆叠无论长短参数,可以以任意顺序出现,也可以任意出现多次。对于多次出现的参数,一般来说是最后一次出现的为准,之前出现过的会被覆盖。 例如命令行: bool型短参数的组合对于getopt不带值的参数,例如
顺序是不敏感的,组合是任意的。 必须带值的参数getopt的定义是参数后加一个冒号,例如 可选值的参数getopt的定义是参数后加两个冒号,例如 在 getopt 界面上的增强命令和子命令以 docker 的子命令 checkpoint 为例: graph LR A[docker] -->|Commands| B(checkpoint) B --> D[create] B --> E[ls] B --> F[rm] 事实上,命令与子命令是没有区别的,如果有必要,可以建立任意多级的命令和子命令嵌套层次。不过在实际的 Command-Line UI 设计中,超过4层的子命令嵌套都是极少数,因为这也会给使用工具的人带来麻烦。 Shell自动完成在现代的命令行界面中,自动完成(Shell Completion)已经是一个关键性特性了。流行的命令行界面例如 Bash、Zsh、Fish 都提供了自动完成的特性。通常一个应用程序需要面向这个Shells 提供配套的自动完成脚本,从而获得自动完成能力。 一个已经支持自动完成的应用程序的命令行输入可能是这样子的: Bash 的自动完成docker 的 自动完成 Zsh 的自动完成docker 在 zsh 中的自动完成。可以注意到 zsh 的 TAB 按键次数更简练,而且列表选择界面也更有效和更具有提示性。当然,zsh的自动完成也存在一些bug,例如一级命令列表超出终端屏幕可视行数时列表选择界面就被破碎掉了。 cmdr 的使用方法
一个简单的入口可以这样: package main import ( "fmt" "github.com/hedzr/cmdr" ) func main() { // logrus.SetLevel(logrus.DebugLevel) // logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true,}) // 可选的四个选项: cmdr.EnableVersionCommands = true cmdr.EnableVerboseCommands = true cmdr.EnableHelpCommands = true cmdr.EnableGenerateCommands = true if err := cmdr.Exec(rootCmd); err != nil { fmt.Printf("Error: %v",err) // or log,logrus } } var( rootCmd = &cmdr.RootCommand{ Command: cmdr.Command{ BaSEOpt: cmdr.BaSEOpt{ Name: "short",Flags: []*cmdr.Flag{ },},SubCommands: []*cmdr.Command{ serverCommands,// msCommands,AppName: "short",Version: cmdr.Version,VersionInt: cmdr.VersionInt,Copyright: "austr is an effective devops tool",Author: "Your Name <[email?protected]>",} serverCommands = &cmdr.Command{ BaSEOpt: cmdr.BaSEOpt{ Short: "s",Full: "server",Aliases: []string{"serve","svr",Description: "server ops: for linux service/daemon.",Flags: []*cmdr.Flag{ { BaSEOpt: cmdr.BaSEOpt{ Short: "f",Full: "foreground",Aliases: []string{"fg",Description: "running at foreground",SubCommands: []*cmdr.Command{ { BaSEOpt: cmdr.BaSEOpt{ Short: "s",Full: "start",Aliases: []string{"run","startup",Description: "startup this system service/daemon.",Action: func(cmd *cmdr.Command,args []string) (err error) { return },{ BaSEOpt: cmdr.BaSEOpt{ Short: "t",Full: "stop",Aliases: []string{"stp","halt","pause",Description: "stop this system service/daemon.",{ BaSEOpt: cmdr.BaSEOpt{ Short: "r",Full: "restart",Aliases: []string{"reload",Description: "restart this system service/daemon.",{ BaSEOpt: cmdr.BaSEOpt{ Full: "status",Aliases: []string{"st",Description: "display its running status as a system service/daemon.",{ BaSEOpt: cmdr.BaSEOpt{ Short: "i",Full: "install",Aliases: []string{"setup",Description: "install as a system service/daemon.",{ BaSEOpt: cmdr.BaSEOpt{ Short: "u",Full: "uninstall",Aliases: []string{"remove",Description: "remove from a system service/daemon.",} ) 可以看到的是,cmdr.RootCommand 和 cmdr.Command 的区别不大,只是多了应用程序信息的成员字段。而 cmdr.Command 和 cmdr.Flag 的区别也不大,它们都有相同的 BaSEOpt 嵌入结构。 因此,定义命令和定义选项是很相似的,然后你需要进行正确的结构嵌套。如果感到嵌套结构迷乱了眼睛,则可以抽出一个子命令、或者一组子命令到一个独立的变量中,然后使用引用的方式嵌入到上级命令的恰当位置。 这种抽出的方式也适合于进行相似结构的共享,但要注意引用和深度拷贝的区别。此处不做进一步讨论了,总之,如果感到没有把握,不妨一级一级地老老实实地完成定义,一般的工具开发也不会有太多的嵌套的吧。 言归正传,完成了上面的定义之后,就可以编译成执行文件运行了(或者用 go run main.go)。你可以在终端中尝试使用它: bin/short bin/short --help bin/short --version bin/short -# bin/short --debug --verbose server --help bin/short svr --help --debug -v bin/short s start -f ~~debug 指定 Action每条命令( 如果有必要的话,一个选项( 对于命令而言,你可以提供额外的 由于
这样的特别逻辑是为了便于开发者定义自己的前置、退出逻辑。例如一个微服务应该在开始提供服务之前完成注册中心登记,以及在停止服务时撤销登记,这些任务适合于在
|