(二)命令行选项
前言
在第一部分,我们实现了一个“简单的命令行程序”-list,用于显示当前目录下的所有文件,文件名之间使用空格分隔。
今天我们一起来增强下 “list” 命令行,给它传递选项。
传递选项的目的
传递选项是为了改变命令行程序的行为。我们之前实现的 list 命令行程序的默认行为是:显示当前目录下的所有文件,文件名称之间使用空格分隔。
现在我们希望通过传递 -l
选项告诉 list 命令行在显示文件名称的时候,每一行只显示一个文件名(即使用换行符分隔)而不是使用空格分隔。
因此我们的目标是:当我们在终端输入以下命令:
list -l
输出当前目录下所有文件的名称,且每行只显示一个文件名。
命令行选项说明
严格来说,命令行选项是键值对,看个例子:
nginx -s reload
我们给 nginx 命令传递了一个选项 ‘-s’,选项的值为 reload。再看一个例子:
ls -l
这里我们给 ls 命令传递一个选项 ‘-l’,但是你会发现这里只有选项,却没有选项的值。一般我们称这种没有值的选项为”开关选项“。可以认为选项的值是 true。当省略选项本身的时候,则相当于:
ls -l false
# 但是我们一般不会这样写,而是直接省略选项
ls
实现方法
回到我们今天的目标本身,实现list -l
。
这里有两个关键问题:
- 如何定义选项
- 如何获取选项的值
在上一篇中,我们实现了列出当前文件夹下的文件名称,并使用空格分隔文件名,主要代码如下:
var rootCmd = &cobra.Command{
Use: "list",
Short: "列出当前文件下的内容",
Long: `列出当前文件下所有的文件名称,包括文件名和目录名。`,
Run: func(cmd *cobra.Command, args []string) {
// 这里写命令行逻辑
listDirContent()
},
}
func listDirContent() {
// 获取当前工作目录
wd, _ := os.Getwd()
// 读取目录下的文件
files, _ := ioutil.ReadDir(wd)
fileNames := make([]string, 0, len(files))
for _, file := range files {
fileNames = append(fileNames, file.Name())
}
fmt.Println(strings.Join(fileNames, " "))
}
如果传递了 “-l” 选项,则使用换行符分隔文件名。使用换行符分隔文件名很简单,我们只需要修改下 listDirContent 方法,给他添加一个参数:分隔符。
func listDirContent(sep string) {
// ... 内容和上面的相同,这里省略。
fmt.Println(strings.Join(fileNames, sep))
}
让我们回到 root.go 中看看 init 方法
func init() {
cobra.OnInitialize(initConfig)
// 定义了config选项
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.xtools-guide.yaml)")
// 定义了toggle选项
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
cobra-init 生成的命令,已经包含了两个选项:一个是 config,一个是 toggle。我们再定义一个 “-l” 选项。
rootCmd.Flags().BoolP("lst", "l", false, "列出文件名,并使用换行符分隔")
可以看到,选项的定义是通过 Flags()
下的各种变体方法完成的。上面的 BoolP 就是用来定义开关型选项的方法。第一个参数是选项的“长名称”,即 lst;第二个参数是选项的“短名称”,即 l;第三个参数是选项的默认值,最后一个参数是选项的帮助信息。
在执行命令的时候,我们可以使用选项“长名称”,也可以使用“短名称”,就拿上面的例子来说,下面两种运行方式是等价的:
list --lst
list -l
注意:长名称前有两个横线,短名称只需要一个横线。长名称更便于理解选项的含义,但是短名称书写更简洁。
一行代码我们就完成了选项的定义,接下来我们要获取选项的值。并根据选项的值决定使用换行还是空格分隔文件名。获取选项的值也很简单:
var rootCmd = &cobra.Command{
Use: "list",
Short: "列出当前文件下的内容",
Long: `列出当前文件下所有的文件名称,包括文件名和目录名。`,
Run: func(cmd *cobra.Command, args []string) {
// 注意:需要通过长名称获取选项的值
value, _ := cmd.Flags().GetBool("lst")
sep := " "
if value {
sep = "\n"
}
listDirContent(sep)
},
}
func listDirContent(sep) {
// ...
}
可以看到获取选项的值是通过 Flags().GetXX()
方法获取,由于我们定义的选项是开关类型,所有通过 GetBool 获取。如果选项是其它类型,还有 GetString、GetInt 等获取选项值的方法。
完整代码放在 github 上,戳这里查看。或者 clone 整个 list 项目,切换到 easy-command-part2 分支查看。
牛刀小试
go run main.go -l
# 或者
go build
./list -l
如果没有意外,应该能看到命令行输入了当前目录下的所有文件名,且每行一个文件名。