Skip to content

高效命令行生活:请吃下这波安利

如果你学习计算机,或者只要你写代码,你就难免离不开和命令行打交道。 很多人都觉得使用命令行界面是一种折磨,而且很难把事情搞对。 这并不是你的错,因为没有配置过的 bash 和 vim 等工具的确难用,而且缺少特性。 但相信我,上古程序员们喜欢使用命令行界面,并把这个传统遗留到今天不是没有理由的。 如果正确配置,并安装使用一些高效的终端工具,一段时间的使用之后你就会发现你的终端生活变得简单且高效。 我之前也非常讨厌命令行界面,但经过了很长时间的探索之后,我觉得自己颇有一番收获,因此写下这篇安利文,希望对仍然挣扎在终端前的你有所帮助。

原理

命令行界面操作的核心要素只有一个,那就是完全依靠键盘完成工作。虽然使用鼠标操作较为直观,不需要记忆按键,但键盘按键操作更加简洁、准确并且迅速。鉴于我们作为计算机工作者会长时间在终端前工作,熟悉按键形成肌肉记忆实际上并不困难,经历过一小段时间的痛苦之后就会感受到好处。

开篇我先介绍一个知道的人不多的事情来佐证我上面的断言。

假设你输入一个非常长的命令,结果你输错了或者不想执行了,你会怎么办?

最直观的做法是按住 backspace 不放,很多人也是这么做的,而我会直接按 ctrl+u,如果你手头有终端,不妨一试。 这个快捷键会把整行命令都删除掉(实际上是删除从光标位置到行首的所有字符)。 这个特性不仅在 bash 下有效,在绝大多数的 shell 里都有效,在 python 的 REPL 命令环境下也有效。 甚至只要看到命令提示符,这个快捷键就有效。

你可能不觉得这有什么奇怪的,但如果你仔细想想这个特性是由谁提供的,事情就变得有趣了。

答案是,这是一个叫做 readline 的 GNU 软件包提供的,在 C 环境下,它的调用只有一句话

#include <stdio.h>
#include <stdlib.h>
#include "readline/readline.h"

int
main(int argc, char** argv)
{
    char* line = readline("> ");
    printf("You entered: \"%s\"\n", line);
    free(line);
    return 0;
}

char* line = readline("> "),其中 "> " 会打印在屏幕上,即提示符( prompt ),而你在输入命令的时候, ctrl-u 就会由 readline 这个函数在背后处理,并把你已经打出的一整行内容删除。当你按下 enter 键,你输入的内容就会被返回到 line 这个指针,由程序进一步处理。

readline 提供的功能可不止删除一整行,

  • ctrl+a 可以跳到命令的行首,这在忘记输入 sudo 时非常有效
  • ctrl+e 跳到行末,可以在补上 sudo 后继续编辑
  • ctrl+k 删除从光标开始到行尾的所有内容,所以除了在行末按 ctrl+uctrl+a 然后 ctrl+k 也可以删除一整行
  • ctrl+b 光标往后退一个位置,比按键盘右下角的左方向键要方便,类似的有 ctrl+f 往前移动一个位置,可以把 b 记忆成 backward 而 f 则是 forward
  • ctrl+w 删除前面一个词,这比删除一个字符往往有效的多,而类似的 alt+balt+f 会往前或者往后把光标挪动一个词的位置
  • ctrl+x, ctrl+e 打开 $EDITOR 环境变量所指代的编辑器来编辑当前命令,默认为 vi,假设你要写一段 bash 脚本,这个命令非常有用
  • 而我们常用的 ctrl+c 发送 SIGINT 信号打断命令执行、ctrl+z 发送 SIGTSTP 挂起命令等也都是由 readline 提供的

所有支持的快捷键可以参考维基百科3,实际上上面的快捷键就是 Emacs 的编辑快捷键。上古时期,没有这些快捷键时,程序员们也是通过左右方向键、backspace 这种方式来编辑命令的,但很快大家就意识到我们需要更高效的快捷键,因此 readline 应运而生,提供了行级别的 Emacs 快捷键来编辑命令。(如你所愿,你可以通过执行 set -o vi 来使用 vi 一致的编辑快捷键,但 vi 和 vim 并不完全兼容,会使得操作不那么顺手)

熟悉这套快捷键会让你的命令编辑效率大大提升,但我要说的不仅仅是这个。readline 这个软件包几乎被使用在任何命令行场景之下,以一种几乎不被用户知道的状态默默提供了一套完整的、一致的命令行界面交互体验支持。而这个软件包自 1994 年开始就由 Chet Ramey 作为唯一的维护着默默地维护着(他同时还参与了 bash 的维护)。20 多年过去了,他从未从 readline 和 bash 的维护中获得过任何报酬4。他的事迹对我的触动很大,因此借次安利之机向大家介绍。

zsh

如果你觉得 bash 难用,不妨尝试一下 zsh 以及其配置 oh-my-zsh。如果你是 macOS 用户,好消息是 Catalina 默认的 shell 将从以往的 bash 换成 zsh,虽然这一举动可能是因为许可证的问题5,但由此也可以看出用 zsh 替换掉 bash 是一个趋势。

安装方法

如果你的系统中不自带 zsh 那么你可以在这里找到安装方法。如 ubuntu 下使用

sudo apt install zsh

即可完成安装。

随后需要安装 oh-my-zsh。没有配置过的 zsh 仍然难用,oh-my-zsh 提供了开箱即用的一套配置方案,可以说没有 oh-my-zsh 就没有这篇推荐文章。

安装 oh-my-zsh 也异常简单,一行即可6

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

(提示:这种一行的命令不要随意地在自己的命令行中执行,因为可能导致危险,特别是要求 sudo 权限执行的脚本。这个一行脚本会从 github 上下载一个脚本,并在本地运行,推荐你去检查一下这个脚本的内容再执行这一行命令)

这样就完成了安装。

这里列举一下非常好用的一些特性

  • up 键会根据已经输入的命令前缀来匹配历史命令,如 git commit -m 然后按 up 键,便可以匹配到之前忘记 git add . 而失败的 git 提交zsh-up
  • 更友好的界面,zsh 可以使用由 oh-my-zsh 提供的丰富的主题,譬如 agnoster. 可以显示当前路径,如果配置了 git 的插件,那么在由 git 管理的目录下可以显示当前项目的状态,譬如当前所在的分支与是否有未被追踪的修改zsh-git-rm

  • 更智能的自动补全。如果在 bash 下使用 cd 命令,按 tab 键补全只会让你在多个选项之间选择,你需要继续输入直到这个前缀唯一;但在 zsh 里,cd 然后按 tab 会列举出所有匹配的目录,让你直接选择zsh-cd

  • 更不用提丰富的插件了,如果用上 zsh-autosuggestions 这个插件,在输入命令时就会得到提示,按 tab 即可补全;而 zsh-syntax-highlighting 则会为命令带上高亮,上面的演示里也展现了个特性
  • 还有一个非常推荐的插件叫 z ,单独拿出来介绍一下。通过 cd 来跳转目录需要你记忆很多路径,也需要输入很长一串字符串,但 z 可以根据你的目录访问历史,通过输入目录路径的子串便可以进行跳转,令人惊喜。zsh-z更多的 zsh 插件可以在这里找到

相比与 bash ,zsh 的可配置程度更高,而且与 bash 兼容,配合 oh-my-zsh 这个开源的配置项目一并食用味道真香。但如果你想要一个开箱即用的友好 shell 环境,可以尝试一下 fish。不过要当心 fish 与 bash 并不兼容,bash 可用的脚本并不能直接在 fish 中执行,特别是包含环境变量的配置的脚本。

tmux

如果没有 tmux,我的命令行生活会黯然无光。

如果你注意到到了前面的演示中最下面的绿光条,那么恭喜你发现了 tmux 的踪迹。tmux 是 terminal mutiplexer 的缩写,可以让你在一个终端窗口中同时执行多个命令行界面。如果你经常打开很多个终端窗口(你肯定会的),并在来回 tab 切换中感到绝望,那么 tmux 就是你的救星。

安装方式请参考 zsh 的安装,搜索操作系统名称加 install tmux 即可。在 archlinux 下

pacman -S tmux

即可完成安装。

tmux 中有多个概念来提供多命令行交互支持

  • pane(面板),一个就是一个命令行交互界面,一个面板也叫 split (待会就明白了)
  • window (窗口),一个窗口可以包含很多面板,而窗口就像浏览器页面一样,因此也叫 tab
  • session (会话),一个会话可以包含很多窗口

如果你感到迷糊的话,接下来我来演示一番。

首先在命令行输入 tmux 然后回车便会打开一个新的 tmux 会话,和原来的界面的区别就是下方多了一个绿光条,左下角的数字是默认的会话名称。tmux 的按键规则是,必须先按一个前置键,然后再按命令键,默认的前置键是 ctrl+b,譬如按 ctrl+b,%(先按 ctrl+b 再按 %% 就是命令键) 就会把当前窗口竖着切开,分裂出一个新的 pane(所以也叫 split)。接下来介绍的快捷键都省略前置键,默认大家都会先按了前置键再按命令键。类似的," 可以把窗口竖着切开,方向键可以在各个面板之间跳转。x 可以杀掉当前的 pane,ctrl+方向键 可以改变面板的大小。

tmux-pane

这些是关于面板的操作,比面板更高的管理层次是窗口。注意上面的演示中左下角的 0:zsh*,其中 0 便是当前窗口的编号;zsh 表示当前窗口的名称,默认是第一个运行的程序于是便是 zsh* 表示所处的窗口为 0 号窗口。是的,如你所愿,我们可以有更多的窗口。按 c 键便可以新建一个新的窗口,并自动分配一个递增的窗口编号。我们可以通过数字键在各个窗口之间跳转,也可以通过 , 给窗口改名,最后通过 & 来杀死一个窗口以及里面所有运行的面板。

tmux-window

更激动人心的是,在窗口之上还有一层,那边是会话层。当你第一次在命令行输入 tmux 并回车时,你便创造了一个 tmux 会话。通过 $ 可以为这个会话改名,这在有多个会话时非常有用。你可以通过 d 来脱离会话(d 含义为 detach),回到熟悉的之前的 zsh 命令行环境。这个时候你可以用 tmux ls 这个命令来查看后台运行的所有 tmux 会话,然后通过 tmux a -t session_name 来恢复会话(a 的含义为 attach)。在 tmux 的会话内部,你也可以切换到其他会话,s 会列出系统中所有的 tmux 会话,通过上下方向键可以选择,也可以按最左边提示的数字键切换,类似的 w 可以列出所有的窗口,因此为窗口改名可以很方便地跳转到之前的工作场景。

tmux-session

除了多路复用一个终端,只要系统不重启 tmux 的会话在脱离的情况下也会保持,这在 ssh 访问远程服务器时非常有用,避免了网络波动 ssh 断开导致的工作被杀。我通常在本地会起几个 tmux 会话,一个叫 home,里面是一些系统监控,如 htop;还有就是手上正在进行的项目的会话,根据项目名称来命名,如 os, tcpreno 等。相信我,用了 tmux 你的命令行工作效率会大大提升。tmux 也可以进行配置、安装插件,譬如复制的插件,但即便不配置,tmux 也仍然非常好用了,聪明的你可以自行去探索进一步配置的方法。

另外说两个也是非常常用的功能。

第一个是翻历史记录。你会发现在 tmux 中没法向上滚动历史。实际上这需要一些特殊操作,先按 ctrl+b 然后再通过 page up 和 page down 进行翻页,在按过一次 page up 之后,你也可以通过上下方向键来滚动,但如果只是 ctrl+b 再按上下键则并不是翻页,考考你是什么呢(不知道的话往前再看一眼或者自己动手试一试)?直接按 q 即可退出滚动历史的模式,回到最下方的命令行。

另一个是复制内容。如果你把窗口竖着切成了两个面板,你会发现用鼠标拖动选择文本复制会跨过面板分割线,复制到另一个面板的内容,着实令人烦躁。我以前是关掉另外一个面板再复制,直到知道可以通过 z 这个命令键来暂时最大化一个面板。因此可以先 ctrl+b,z 最大化面板,鼠标拖动复制,再 ctrl+b,z 退出最大化模式。

tmux-another2

如果你觉得暂时记不清楚,我强烈鼓励你先用起来。我在写这篇文章的时候,最犯难的事情就是不记得这些按键是什么,不得不打开 tmux 先尝试一番。因为我只有肌肉记忆,从来就没有特意记过这些按键。另外,在 tmux 会话中按 ? 便可获得快捷键列表清单的帮助页面(按 q 退出),方便你及时查询。

vim

最后介绍一点 vim 的骚操作。可能你已经用过 vim,也听说过 how to quit vim 这个问题在 stackoverflow 上帮助近百万人退出 vim7,但我想给你看下面这张图片

editor-learning-curve

这张图片是好事者制作的各大编辑器的学习曲线,横轴是掌握程度(或者学习时间),纵轴是编辑效率。我对编辑器没有喜恶,因此不评价其他的编辑器,但 vi/vim 这个曲线倒是确实说明了问题。vim 比较难以学习,但经历过一些折腾真正熟悉了起来,就会得到很大的效率提升。我从 13 年开始就使用 vim,至今已经六年。但我从不认为我掌握了这个编辑器,我还需要继续学习和操练。但我真正 vim 使用水平得到提高,也是最近开始刻意练习的结果。相信我,如果你刻意地去学习和使用,你的 vim 水平很快就能赶上我。

关于 vim 的资料不胜枚举,这里我不烦列了。但我想指出,vim 最完整的学习资料来自于其完善的帮助手册。在 vim 里按 f1 即可看到。

vim 两大骚操作,第一个是块状编辑,这个可能大家都知道。我主要介绍另外一个,也就是宏录制。我们可以把宏理解成一套操作流程,录制宏就是录制一个编辑流程,而重放宏则是把这个编辑流程进行重放,通过指定重放次数,可以以类似循环的方式多次重放。

请看下面的演示。编辑场景是有很多的错误码,我想要修改其中连续的几行的错误码和前面的错误码顺序接上。如果不用 vim 一行的手改实在是繁琐,而使用编辑宏则非常简单。在 normal 模式下按 q 再另外按一个字母键,譬如 aqa 即可开始录制,左下方也会显示 recording@a。我录制的编辑流程是,从 = 这个位置开始,先复制上一行的错误码,然后回到本行粘贴,再 ctrl+a 对这个错误码进行加一操作,再跳到下一行的 = 的位置。这样每一行的 = 就成了位置标定,类似于一个循环不变量,再重新执行一遍之前的编辑流程就会把这行的错误码设置成前一行加一。通过再次按 q 键结束录制,这样这套编辑流程就会被命名为 a。最后按 12@a 便可以把这个编辑流程重放 12 次,顺利完成了编辑操作。这套宏编辑的方法实在是太好用了,配合前述的块编辑模式,为我解决了很多问题,因此强烈安利。

change-error-code

最后

工欲善其事,必先利其器

这句话实在是太对了。我最近对这一点的感触颇深。人性总是喜懒的,懒得学、懒得用太正常不过了,但刻意地跳出舒适圈,花一点时间去学习掌握更好用的工具,提高自己工作的效率,你会感受到幸福的。

杜威在 Art as Experience 中说

The intelligent mechanic engaged in his job, interested in doing well and finding satisfaction in his handiwork, caring for his materials and tools with genuine affection, is artistically engaged.

祝诸君能在工作中找到快乐,于代码中发现美感,从工具中感受到艺术。

致谢

本文中的所有操作示意 gif 都是用 peek2 和 screenkey 1 这两个工具录制的,其中 peek 可以选择屏幕的一块区域录制 gif,而 screenkey 则可以把按键显示在屏幕上。

同时感谢先驱们为我们提供的工具,这些工具无一不是开放源代码的免费软件,这种精神值得尊敬。



  1. https://www.thregr.org/~wavexx/software/screenkey/ 

  2. https://github.com/phw/peek 

  3. https://en.wikipedia.org/wiki/GNU_Readline 

  4. https://twobithistory.org/2019/08/22/readline.html 

  5. https://www.theverge.com/2019/6/4/18651872/apple-macos-catalina-zsh-bash-shell-replacement-features 

  6. https://github.com/ohmyzsh/ohmyzsh 

  7. https://stackoverflow.blog/2017/05/23/stack-overflow-helping-one-million-developers-exit-vim/