现代化命令行工具相比于传统命令行工具,其汲取了现代 web 的优秀的 UI 和交互设计,所以对用户更加友好, 同时也保留了命令行操作的高效性,能让用户体会到hack的乐趣。本片博文,将给大家讨论如何构建现代化命令行工具。 首先我会结合自身的经历,来谈谈为什么命令行是高效的,然后对比两组常用且核心功能相同的命令行工具, 让大家知道现代命令行工具一般具备哪些特征,最后依据这些特征,给大家总结使用 go 语言构建现代命令行的过程, 以及常用的库。

为什么偏爱命令行

作为一位 Hacker,我十分推崇键盘操作模式以终端为中心的工作模式这两种理念哲学。很大程度上,这两种理念哲学是 Hacker 文化的重要部分。

键盘操作,与之相对应的是鼠标(触屏等)操作,这两种模式是目前的主流人机交互方式。对于使用过VIMWord两种工具的用户, 会很容易理解,两种模式的区别。word如今使用更加广泛,并且鼠标操作模式也一直是主流,而VIM的使用人群则相对固定(开发者), 对于一个深度使用VIM的人是很难抛弃它,VIM 吸引用户的魔力正是键盘操作模式,而 VIM 正是以键盘为中心操作模式的代表, 这种设计哲学几乎在很多专业软件都有体现,比如Jetbrains这家公司的产品,主要提供流行语言的 IDE 产品, 其核心特色就是通过快捷键能够实现几乎所有功能(Jetbrains 定义为Action),并且这些快捷键很容易定制,同时有Find Action统一的入口。 这也是 Jetbrains 产品的优势所在,再比如VSCode编辑器,能够从众多编辑器脱颖而出也正是因为其设计广泛采用了这种模式。

这两种模式在不同场景各有优势,鼠标能够让用户更容易上手,降低用户学习成本。 而对于开发者,键盘操作模式能显著提升生产力,还有在一些专业工具中, 大量使用快捷键代替繁琐的鼠标操作,能够使开发人员更加专注于工作,提高效率,因为键盘操作模式更容易产生肌肉记忆。

终端(终端模拟器)作为计算机发展的早期人与计算机进行的通信的工具,即使到现在,仍然无法被取代,并且相对于各种形形色色的程序,其仍然是最特殊和最具个性的一个。 终端的使用主要是通过命令行工具完成具体的工作,由于早期没有鼠标,终端的工作模式以键盘为主,这里提一下, 现代的终端程序也提供了对键盘的支持。微软个人PC早期是十分不看重终端程序的,但是现在却也大力发展终端(这里微软终端开源仓库), 其主要原因是不想白白失去开发者这一膨大的用户群体,从中也可以看出,终端的重要地位。

上图是微软大力研发的命令行工具,对比传统的CMD程序,我们能够从中发现现代命令行工具的影子。

如何设计现代的命令行工具。

良好的终端的体验通常包含以下四种工具的选择:

  • 终端模拟器的选择,优秀终端模拟器一般提供良好的 UI 设计,分屏,Session 管理等;比如 Mac 用户通常会使用 Iterm2 而不是自带终端程序, 正是 Iterm2 的用户体验更加出色,功能更加齐全;
  • Shell 的选择,Shell 发展至今,也有很多选择,优秀的 Shell 提供自动提示,插件集成,良好的 UI 等,注意并不是每个系统都对所有类型 Shell 提供支持的, 所以选择通用 Shell 会降低后续迁移的风险,Oh-My-Zsh 是现代 Shell 中的佼佼者;
  • 包管理方式,当命令行工具变得越来越多,所带来的管理代价也越来越大,包管理工具能够帮助管理安装,卸载,升级等, 包管理工具也是命令行工具;
  • 命令行工具的选择,如何在众多质量层次不齐的工具中选择,这很大程度影响了用户体验,这是本文后续重点的讨论的内容;

命令行工具通常实现一组相关的功能,功能是吸引用户使用的最重要的点,不过这里我们无法讨论功能的好坏, 但是如果有一组实现相同功能的命令行工具给你选择,你该如何选择呢?毫无疑问,你会使用更简洁,方便的那一个, 这就是命令行工具的用户体验的重要性,让你的用户留下来

RTFM(阅读该死的手册Read The Fucking Manual)文化在命令行工具的用户群体的流行,也是对许多命令行工具的吐槽, 甚至,正是因为命令行工具的不易使用,才会让很多人觉得命令行很高大上。出现这一现象的原因,一是命令行工具大都是开源工具, 开发质量无法得到保证,并且无法跟上现代用户的使用需求。二是大多数公司更重视应用 APP 和浏览器的设计和用户体验的优化, 忽视命令行工具的优化。例如 Github 这样的企业到今年才有了官方的命令行工具cli。三是用户群体的忍耐性高,命令行工具的用户群体, 似乎是习惯了其糟糕的体验。

说到这里,什么样的命令行工具可以称得上现代的?用户体验优秀的命令行工具和普通命令行工具有什么区别呢? 事实上,也确实没有相关衡量标准的建立,不过,下面通过对比两组工具(没有使用过的读者可以试着使用),相信大家心中会有答案。

mysql VS mycli

以上两款工具都是mysql的客户端,但是大多数用户在使用 mycli 后会选择它,因其提供使用者良好的补全,同时界面的 UI 也更加现代。

python VS ipython

以上是两种 python 交互 Shell,ipython 提供的提示更加方便,同时也能方便查阅读文档,方便导出代码段,查询操作历史等功能, 这些功能相当实用。

大家可以对比使用以上两组工具,更能够明白一个良好的用户体验为什么能让用户留下来。虽然目前没有事实的标准, 但是现代的命令行工具一般具有以下特点:

  • 良好的提示和补全,特别提一点动态信息的补全,如 mycli 能够补全字段,表和数据库这些信息;
  • 现代 UI 设计,如对于重点数据使用颜色标注,长时间的操作提供状态条,输出的内容排版等;
  • 遵循标准统一规范(Unix/Posix,BSD,GNU风格的参数),不滥用Flag,Args,Subcommand这些元素。比如开关这种动作应该使用子命令而不是通过 flag 控制。 统一规范有利于用户快速上手;
  • 详细有序的输出信息,如在打印一组规则的输出时,表格会更加合理;
  • 便携的输入,在一些情况使用交互式输入,会对用户更优化,对于需要输入如 yaml 等格式的文件,使用默认编辑器会更合理。

go构建现代命令行工具及相关库整理

目前,我们已经可以通过很多工具构建命令行工具,最简单原始的方式就是通过 Shell 脚本,各个语言也都有命令行开发库和工具, 在所有语言中最常见也是最适合开发的语言是Cnodepython。为什么这三种语言比较合适,这里就不再赘述了。

随着Golang这几年的发展,相关库生态的丰富,加上Golang语言本身很适合构建命令行工具(最终构建物就是可执行文件), 所以也出现大量通过 Go 开发的命令行工具,如kubectl,hugo,cli等。下面结合自己的使用经历和经验, 给大家整理了go 如何选择合适工具来开发现代命令行工具。

对于下面的工具和库,这里只做简单的介绍,详细用法参考使用文档。

根据上述我们总结的优秀命令行工具的特点,我们来选择适合的库。首先选择框架,目前使用比较多的有cobracli, 由于已有许多成熟的项目使用了cobra,所以建议使用这个库,可以参考其他的项目中优秀的设计模式,上手很简单, 下面是一个简单的示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var rootCmd = &cobra.Command{
Use: "hugo",
Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at http://hugo.spf13.com`,
Run: func(cmd *cobra.Command, args []string) {
// Do Stuff Here
},
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func main() {
cmd.Execute()
}

这个库能够方便实现子命令(嵌套命令),同时能够自动生成帮助信息(help)和手册(man page),也能够生成补全信息, 命令行参数也符合 Posix 的标准,其为我们做了很多工作,所以我们已经成功一大半了。底层搭好了,剩下的就是美化的工作。

格式化输出

首先是输出的格式化,用户通过命令行获取信息,能够让用户快速定位到关键信息很重要,研究表明, F型,分层蛋糕型和斑点型的输出是最友好的三种输出模型。不要让输出过分紧凑,适当的保留空白区域,有利于降低用户的阅读难度。

列表输出是最常见的一种形式,go 中通过库 Tablewriter 能够很好的处理表格的格式化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data := [][]string{
[]string{"A", "The Good", "500"},
[]string{"B", "The Very very Bad Man", "288"},
[]string{"C", "The Ugly", "120"},
[]string{"D", "The Gopher", "800"},
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Sign", "Rating"})

for _, v := range data {
table.Append(v)
}
table.Render() // Send output
1
2
3
4
5
6
7
8
+------+-----------------------+--------+
| NAME | SIGN | RATING |
+------+-----------------------+--------+
| A | The Good | 500 |
| B | The Very very Bad Man | 288 |
| C | The Ugly | 120 |
| D | The Gopher | 800 |
+------+-----------------------+--------+

对于其他固定类型的可视化,可以使用termui,这个工具提供了很多小组件,如饼图,柱状图等格式化数据,有需要的小伙伴, 可以自行了解学习。

色彩

添加各种色彩有很多好处,一方面可以提醒用户重要的信息,另一方面一些颜色本身也具备传递信息的功能。如红色通常代表错误和危险操作, 绿色通常代表成功,黄色提醒需要注意,蓝色代表着状态良好。配合终端模拟器,合理使用色彩,会让你的命令行工具更加出彩。

go 中通过 Color 能够打印带有色彩的文本信息,对于 Log 的打印,logrus 库也提供了彩色支持。

在输出中添加图片和 logo 能够拉近与用户的距离,同时图片相比文字能够传递更多的信息,更容易被用户接受, 有的终端模拟器可能不支持图片,所以在使用中需要考虑所面向的用户,及其使用环境。

状态条

一些耗时操作,如上传下载文件,如果没有任何输出,这可能会导致用户强行关闭程序,通过一个实时状态条, 就能轻松过解决。Go 中有很多这样的库如 progressbar, cheggaaa/pb 等。下面是简单的 cheggaaa/pb 的 Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
count := 100000
// create and start new bar
bar := pb.StartNew(count)

// start bar from 'default' template
// bar := pb.Default.Start(count)

// start bar from 'simple' template
// bar := pb.Simple.Start(count)

// start bar from 'full' template
// bar := pb.Full.Start(count)

for i := 0; i < count; i++ {
bar.Increment()
time.Sleep(time.Millisecond)
}
bar.Finish()

构建一个用户体验优秀的命令行工具,以上几个方面是非常值得考虑的。当然除了以上几点, 你还需要考虑如何发布你的工具,版本升级,文档维护等问题。

除了借鉴开源的命令行工具,大家也可以参考项目 hack, 这是我开源的一个命令行工具,主要功能是简化Mac工作流,同时在实现功能的同时也在尝试构建现代化命令行工具, 目前已经有以上相关优化的实践样例。

总结

这篇博文,对于哪些深度使用命令行工具的读者,可能会更容易体会其价值。一个排斥命令行工具的人,可能不容器理解, 当然,这也很正常,现在想起刚接触命令行工具的自己,同样也是排斥的。我想告诉这些读者,如果你是一位开发者, 那么请先尝试拥抱命令行工具,再回头来阅读,你应该会有所收获。

如果想要快速构建命令行工具,尝试通过创建一个符合自己用户习惯的命令行工具(如将多个命令行工具的功能聚合成一个的形式),用来简化日常工作流。就像hack项目一样, 相信你会很快上手。