(一)最简单命令行

Last Modified: 2023/08/13

前言

这是一个编写命令行程序的“有手就能写”系列教程,跟着做,你也可以编写装逼命令行程序。在黑乎乎终端窗口中敲下你自己编写的命令行程序,输出熟悉的“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 都可以很容易的实现。

有问题吗?点此反馈!

温馨提示:反馈需要登录