(一)最简单命令行
前言
这是一个编写命令行程序的“有手就能写”系列教程,跟着做,你也可以编写装逼命令行程序。在黑乎乎终端窗口中敲下你自己编写的命令行程序,输出熟悉的“hello world”,成就感只能自己体会。
这一切的前提是,你得会go的基本语法,你得知道什么是命令,以及如何在终端运行命令。所以说有手也不一定能写。
课程目标
这篇文章中,我们的目标是编写一个和 ls 类似的命令行程序。为了避免和系统的命令行程序同名,我们的命令行程序就叫 list 吧。
在这之前,先熟悉下系统自带的 ls 命令,打开终端,输入 ls
,看看该命令的输出:
cd /tmp
ls
ls
命令会输出当前目录下所有的文件和文件夹,由于我们先使用 cd /tmp
进入了 /tmp 目录,所以 ls
会列出 /tmp 目录下的所有文件和文件夹。
在我的电脑上,上面的命令会输出以下内容:
hsperfdata_saltyfish sogouimebs-qimpanel-watchdog-1000:1.pid
snap.snap-store
由于篇幅关系,输出内容有删减,仅供参考,而且每个人的电脑的 /tmp 目录下的内容可能都不太一样,知道 ls
的作用即可。
准备命令行 - list
工欲善其事,必先利其器,我们要使用的命令行利器叫 cobra。
第一步:当然是建立一个 Go 模块。
mkdir -p $HOME/Codes/guides && cd $HOME/Codes/guides
mkdir list
cd list
go mod init github.com/isaltyfish/list
通过以上步骤,我们在 $HOME/Codes/guides 目录下创建一个 list 模块。注意:模块名称中的 isaltyfish 是笔者自己的 github 用户名,你需要替换成自己 github 用户名。
第二步:使用 cobra 配套的 cobra-cli 初始化命令行应用。
# 安装最新的 cobra-cli,如果已经安装,则跳过
go install github.com/spf13/cobra-cli@latest
# 使用 cobra-cli 初始化我们 list 模块
cd $HOME/Codes/guides/list
cobra-cli init
如果运行 cobra-cli init
提示错误:command not found: cobra-cli。说明你没有将 $GOPATH/bin
目录放到 $PATH
中。
此时我们也通过全路径运行init:
$GOPATH/bin/cobra-cli init
init 会在我们的 list 模块下会自动生成 main.go 和 cmd 目录,cmd 目录下当前只有一个文件 root.go。root.go 就是我们编写命令行逻辑的地方。
下面的代码片段取自 root.go,cobra.Command
代表一个命令,其中 Short 字段,是对命令的简要描述,Long 字段是对命令的详细描述。
var rootCmd = &cobra.Command{
Use: "list",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application。`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
这些描述信息是给使用者的帮助信息,例如用户输入 list -h
的时候,会将你填写的内容作为帮助信息提示给用户。我们修改下让他更贴合 list 程序。
var rootCmd = &cobra.Command{
Use: "list",
Short: "列出当前文件下的内容",
Long: `列出当前文件下所有的文件名称,包括文件名和目录名。`,
}
顺便提一下,系统的 ls 命令的功能是很强的,我们的 list 程序只是展示作用的一个玩具,不会实现和 ls 完全一样的功能。
编写逻辑
编写命令行的逻辑,我们只需要在 Command 提供 一个 Run
字段,字段的值是一个函数,函数体就是书写逻辑的地方。
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, " "))
}
完整代码放在 github 上,戳这里查看。或者 clone 整个 list 项目,切换到 easy-command-part1 分支查看。
牛刀小试
一切就绪,运行下我们的命令:
go run main.go
在开发环境下,可以通过上面的命令快速验证我们的命令是否正确。命令编写完毕后,我们可以打包命令,并将命令放到 $PATH 下,方便我们随时运行命令。
如果 $GOPATH/bin 已经加入到 $PATH 中。运行一下命令即可。
go install
go install 会打包程序,程序的名称为 list,同时 list 程序会被复制到 $GOPATH/bin中。万事俱备,让我们试试 list 命令行程序。
cd /tmp
list
如果运行以上命令后在控制台输出了 /tmp 目录下所有文件名,说明一切工作正常。同时我们的命令行已经具备帮助信息。
list -h
#或者
list --help
结语
有爱琢磨的同学,可能会冒出一个大大的疑问,写一个 list 命令行程序需要那么复杂吗?直接在 main.go 调用 listDirContent()
不就完事了吗?
func main() {
listDirContent()
}
并不是,使用 cobra 让我们的命令直接达到专业命令行的水准。上面的写法虽然也实现了同样的功能,但是没有帮助信息,此其一;另外命令行程序可能会带很多选项,解析命令行选项也不是件容易的事。命令行还可能包含子命令,通过 cobra 都可以很容易的实现。