<audio id="audio" title="27 | 命令行：不只是酷，更重要的是能提高个人效能" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d3/4a/d305df11cd909f9146d06d90f8924d4a.mp3"></audio>

你好，我是葛俊。今天，我要与你分享的主题是，命令行下的高效工作技巧。

我先和你讲一个有意思的话题吧。命令行工具常常会给人一种黑客的感觉，好莱坞的电影里面常常出现命令行窗口的使用。不知道你听说过没有，很多好莱坞电影在拍摄时使用的其实是一个叫作[nmap](https://nmap.org)的工具。这个工具是做安全扫描的，只不过因为它的显示特别花哨，所以被很多电影采用。在nmap官方网站上，还专门列出来了这些电影的名单。

类似这种可以让自己看起来很忙的工具还有很多，比如[Genact](https://github.com/svenstaro/genact)。下面是一个使用Genact的录屏，当然这里的命令并没有真正运行。这可能是整个专栏中，唯一一个让你看起来效率很高，实际上却是降低效率的工具，但说不定对你有用，你懂的。

<img src="https://static001.geekbang.org/resource/image/2d/97/2dba0dfdd5f0260ad3b3082a7d3d2697.gif" alt="">

讲完这个娱乐性的话题，我们进入正题吧。

## 为什么要使用命令行？

GUI图形界面的出现是计算机技术的变革，极大方便了用户。但在这么多年后的今天，命令行工具为什么仍然有如此强大的生命力呢？

在我看来，对软件工程师来说，想要高效开发就必须掌握命令行，主要原因包括：

- 虽然鼠标的移动和点击比较直观，但要完成重复性的工作，使用键盘会快捷得多。这一点从超市的结算人员就可以看出来，使用键盘系统的收银员总是噼里啪啦地很快就可以完成结算，而使用鼠标点击的话明显慢很多。
- 作为开发人员，可以比较容易地使用命令行的脚本，对自己的工作进行自动化，以及和其他系统工具联动。但使用GUI的话，就会困难得多。
- 命令行通常可以暴露更完整的功能，让使用者对整个系统有更透彻的理解。Git就是一个典型的例子，再好的GUI Git系统都只能封装一部分Git命令行功能，要想真正了解Git，我们必须要使用命令行。
- 有一些情况是必须使用命令行的，比如SSH到远程服务器上工作的时候。

为了演示命令行的强大功能给我们带来的方便，下面是一个在本地查看文件并上传到服务器的流程的录屏。

<img src="https://static001.geekbang.org/resource/image/1d/29/1deb0006ea65cd5c4a28819a2cf50629.gif" alt="">

<img src="https://static001.geekbang.org/resource/image/95/40/958987c6594699ff1fb97b1622b21a40.gif" alt="">

通过这个案例，你可以看到命令行的以下几个功能：

- 在提示行会高亮显示时间、机器名、用户名、Git代码仓的分支和状态，以及上一个命令的完成状态。
- 输入命令的时候，高亮显示错误并自动纠错。
- 使用交互的形式进行文件夹的跳转，并方便查找文件，还可以直接在命令行里显示图片。
- 使用交互的工具，把文件上传到远端的服务器，并快速连接到远端查看传输是否成功。

整个流程全部都是在命令行里完成的，速度非常快，用户体验也非常好。正因为如此，我看到的硅谷特别高效的开发人员，绝大多数都大量使用命令行。那，面对成百上千的命令行工具，我们**怎样才能高效地学习和使用**呢？

我将高效学习使用命令行的过程，归纳为两大步：

1. 配置好环境；
1. 针对自己最常使用命令行的场景，有的放矢地选择工具。

今天，我就与你详细讲述**环境配置**这个话题。而关于选择工具的话题，我会在下一篇文章中与你详细介绍。总结来讲，环境配置主要包括以下四步：

1. 选择模拟终端；
1. 选择Shell；
1. 具体的Shell配置；
1. 远程SSH的处理。

这里需要注意的是，在命令行方面，macOS和Linux系统比Windows系统要强大许多，所以我主要以macOS和Linux系统来介绍，而关于Windows的环境配置，我只会捎带提一下。不过，macOS和Linux系统中的工具选择和配置思路，你可以借鉴到Windows系统中。

## 第一步，选择模拟终端

我将一个好的终端应该具有的特征，归纳为4个：

- 快，稳定；
- 支持多终端，比如可以方便地水平和纵向分屏，有tab等；
- 方便配置字体颜色等；
- 方便管理常用SSH的登录。

macOS系统自带的终端不太好用，常见的替代工具有iTerm2、Terminator、Hyper和Upterm。我平时使用的iTerm2，是一个免费软件，功能强大，具备上面提到的4个特征。下面我就以iTerm2为例展开介绍。其他几个工具上也有类似功能，所以你不必担心。

在多终端的场景方面，iTerm2支持多窗口、多标签页，同一窗口中可以进行多次水平和纵向分屏。这些操作以及窗口的跳转都有快捷键支持，你可以很方便地在网络上搜索到。

<img src="https://static001.geekbang.org/resource/image/d6/bf/d63c4054e8e23136da22b1d9d6e4c2bf.png" alt="">

在管理常用SSH的登录方面，iTerm2使用Profile（用户画像）来控制。比如，下面是一个连接到远程服务器案例的录屏。

可以看到，在我的工作环境里常会用到4个Profile，其中有两个是连接到远端服务器的，包括Mosh Remote Server 1和SSH Remote Server 2。工作时，我使用Cmd+O，然后选择Server 1这个Profile，就可以打开一个新窗口，连接到这个远程服务器上。

<img src="https://static001.geekbang.org/resource/image/ec/ae/ec60c3bf9bb2688ea59e76b1576078ae.gif" alt="">

每一个Profile都可以定义自己的字体、颜色、shell命令等。比如，Server 1是类生产服务器，我就把背景设置成了棕红色，提醒自己在这个机器上工作时一定要小心。所以在上面的录屏中你可以看到，连接到远端的SSH标签页，它的背景、标签页都是棕红色。另外，下面是如何对Profile颜色进行设置的截屏。

<img src="https://static001.geekbang.org/resource/image/54/58/54888d3d58612f9b557af6e45f40b758.png" alt="">

除了这些基础功能外，iTerm2还有很多贴心的设计。比如：

- **在屏幕中显示运行历史**（Cmd+Opt+B/F）。有些情况下，向上滚动终端并不能看到之前的历史，比如运行VIM或者Tmux的时候。这时，浏览显示历史就特别有用了。
- **高亮显示当前编辑位置**，包括高亮显示当前行（Cmd+Opt+；）高亮显示光标所在位置（Cmd+/）。
- **与上一次运行命令相关的操作**，包括显示上一次运行命令的地方（Cmd+Shift+up），选中上一个命令的输出（Cmd+Shift+A）。

其中第2、3项功能是由一组[macOS的集成工具](https://www.iterm2.com/documentation-utilities.html)提供的。这个工具集还包括显示图片的命令imgls、imgcat，显示自动补全命令，显示时间、注释，以及在主窗口旁显示额外信息等。这些设计虽然很小，但非常实用。

关于Windows系统，2019年5月微软推出了[Windows Terminal](https://devblogs.microsoft.com/commandline/introducing-windows-terminal/)，支持多Tab，定制性很强，据说体验很不错。

选择好了终端，环境设置的第二步就是选择Shell。

## 第二步，选择Shell

在我看来，选择Shell主要有普遍性和易用性这两条原则。

Linux/Unix系统下，**Bash**最普遍、用户群最广，但是易用性不是很好。常用来替代Bash的工具有**Zsh**和**Fish**，它们的易用性都很好。下面是两张图片，用于展示Zsh和Fish在易用性方面的一些功能。

Zsh：

<img src="https://static001.geekbang.org/resource/image/31/a6/3198a7c972939d35de0c03bd26c4a8a6.gif" alt="">

Fish：

<img src="https://static001.geekbang.org/resource/image/08/32/082720f4dbe7e335a290938993d84732.gif" alt="">

我个人觉得Fish比Zsh更方便。事实上，Fish是Friendly Interactive Shell的简称。所以，交互是Fish的强项。可惜的是，Fish不严格遵循POSIX的语法，与Bash的脚本不兼容，而Zsh则兼容，所以我目前主要使用的是Zsh。

选好了模拟终端和Shell之后，便是配置环境的第三步，具体的Shell配置了。

## 第三步，具体的Shell配置

接下来，我以我自己使用的设置为例，向你介绍Bash、Zsh、Fish的具体配置吧。这里，主要包括**命令行提示符**的配置和其他配置两个方面。

之所以把命令行提示符单独提出来，是因为它一直展现在界面上，能提供很有用的价值，对命令行高效工作至关重要。下面是一张图片，展示了Bash、Zsh和Fish的命令行提示符。

<img src="https://static001.geekbang.org/resource/image/cd/bf/cd10736ba6a970f9a7f8d2703a4b45bf.png" alt="">

这个窗口分为三部分，最上面是Bash，中间是Zsh，最下面是Fish，都配置了文件路径、Git信息和时间戳等信息。接下来，我带你一起看看这3个工具应该如何配置吧。

**Bash比较麻烦，**配置文件包括定义颜色和命令行提示符的两部分：

```
## 文件 $HOME/.bash/term_colors，定义颜色
# Basic aliases for bash terminal colors
N=&quot;\[\033[0m\]&quot;    # unsets color to term's fg color

# regular colors
K=&quot;\[\033[0;30m\]&quot;    # black
R=&quot;\[\033[0;31m\]&quot;    # red
G=&quot;\[\033[0;32m\]&quot;    # green
Y=&quot;\[\033[0;33m\]&quot;    # yellow
B=&quot;\[\033[0;34m\]&quot;    # blue
M=&quot;\[\033[0;35m\]&quot;    # magenta
C=&quot;\[\033[0;36m\]&quot;    # cyan
W=&quot;\[\033[0;37m\]&quot;    # white

# empahsized (bolded) colors
MK=&quot;\[\033[1;30m\]&quot;
MR=&quot;\[\033[1;31m\]&quot;
MG=&quot;\[\033[1;32m\]&quot;
MY=&quot;\[\033[1;33m\]&quot;
MB=&quot;\[\033[1;34m\]&quot;
MM=&quot;\[\033[1;35m\]&quot;
MC=&quot;\[\033[1;36m\]&quot;
MW=&quot;\[\033[1;37m\]&quot;

# background colors
BGK=&quot;\[\033[40m\]&quot;
BGR=&quot;\[\033[41m\]&quot;
BGG=&quot;\[\033[42m\]&quot;
BGY=&quot;\[\033[43m\]&quot;
BGB=&quot;\[\033[44m\]&quot;
BGM=&quot;\[\033[45m\]&quot;
BGC=&quot;\[\033[46m\]&quot;
BGW=&quot;\[\033[47m\]&quot;

```

```
## 文件 $HOME/.bashrc，设置提示符及其解释
###### PROMPT ######
# Set up the prompt colors
source $HOME/.bash/term_colors
PROMPT_COLOR=$G
if [ ${UID} -eq 0 ]; then
  PROMPT_COLOR=$R ### root is a red color prompt
fi

#t Some good thing about this prompt:
# (1) The time shows when each command was executed, when I get back to my terminal
# (2) Git information really important for git users
# (3) Prompt color is red if I'm root
# (4) The last part of the prompt can copy/paste directly into an SCP command
# (5) Color highlight out the current directory because it's important
# (6) The export PS1 is simple to understand!
# (7) If the prev command error codes, the prompt '&gt;' turns red
export PS1=&quot;\e[42m\t\e[m$N $W&quot;'$(__git_ps1 &quot;(%s) &quot;)'&quot;$N$PROMPT_COLOR\u@\H$N:$C\w$N\n\[&quot;'$CURSOR_COLOR'&quot;\]&gt;$W &quot;
export PROMPT_COMMAND='if [ $? -ne 0 ]; then CURSOR_COLOR=`echo -e &quot;\033[0;31m&quot;`; else CURSOR_COLOR=&quot;&quot;; fi;'

```

命令行提示符之外的其他方面的配置，在Bash方面，我主要设置了一些命令行补全（completion）和别名设置（alias）：

```
## git alias
alias g=git
alias gro='git r origin/master'
alias grio='git r -i origin/master'
alias gric='git r --continue'
alias gria='git r --abort'


## ls aliases
alias ls='ls -G'
alias la='ls -la'
alias ll='ls -l'


## git completion，请参考https://github.com/git/git/blob/master/contrib/completion/git-completion.bash
source ~/.git-completion.bash

```

**Zsh的配置就容易得多了，而且是模块化的**。基本上就是安装一个配置的框架，然后选择插件和主题即可。具体来说，我的Zsh命令行提示符配置步骤包括以下三步。

第一，[安装oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh)。这是一个对Zsh进行配置的常用开源框架。

```
brew install zsh

```

第二，[安装powerline字体](http://powerline)，供下一步使用。

```
brew install powerlevel9k

```

第三，在~/.zshrc中配置ZSH_THEME，指定使用powerlevel9k这个主题。

```
ZSH_THEME=&quot;powerlevel9k/powerlevel9k&quot;

```

命令行提示符以外的其他配置，主要是通过安装和使用oh-my-zsh插件的方式来完成。下面是我使用的各种插件，供你参考。

```
## 文件~/.zshrc.sh 中关oh-my-zsh的插件列表，具体插件细节请参考https://github.com/robbyrussell/oh-my-zsh，以及使用Web搜索查询
plugins=(
  git
  z
  vi-mode
  zsh-syntax-highlighting
  zsh-autosuggestions
  osx
  colored-man-pages
  catimg
  web-search
  vscode
  docker
  docker-compose
  copydir
  copyfile
  npm
  yarn
  extract
  fzf-z
)

source $ZSH/oh-my-zsh.sh

```

**至于Fish的配置，和Zsh差不多，也是安装一个配置的框架，然后选择插件和主题即可。**在配置命令行提示符时，主要步骤包括以下两步。

第一，安装配置管理框架oh-my-fish：

```
curl -L https://get.oh-my.fish | fish

```

第二，查看、安装、使用oh-my-fish的某个主题，主题会自动配置好命令行提示符：

```
omf theme
omf install &lt;theme&gt;
omf theme &lt;theme&gt;

## 我使用的是bobthefish主题
omf theme bobthefish

```

这里有一篇不错的关于[使用oh-my-fish配置的文章](https://www.ostechnix.com/oh-fish-make-shell-beautiful/)，供你参考。

Fish的其他方面的配置，也是使用oh-my-fish配置会比较方便。关于具体的配置方法，建议你参照[官方文档](https://github.com/oh-my-fish/oh-my-fish)。

关于环境的最后一个配置，是远程SSH的处理。

## 第四步，远程SSH的处理

SSH到其他机器，是开发人员的常见操作，最大的痛点是，怎样保持多次连接的持久性。也就是说，连接断开以后，远端的SSH进程被杀死，之前的工作记录、状态丢失，导致下一次连接进去需要重新设置，交易花销太大。有两类工具可以很好地解决这个问题。

**第一类工具是Tmux或者Screen**，这两个工具比较常见，用来管理一组窗口。

接下来，我以Tmux为例，与你描述其工作流程：首先SSH到远程服务器，然后用远程机器上的Tmux Client连接到已经运行的Tmux Session上。SSH断开之后，Tmux Client被杀死，但Tmux Session仍然保持运行，意味着命令的运行状态继续存在，下次SSH过去再使用Tmux Client连接即可。

如果你想深入了解Tmux的概念和搭建过程，可以参考[这篇文章](http://cenalulu.github.io/linux/tmux/)。

**第二类是一个保持连接不中断的工具，移动Shell**（Mobile Shell）。这也是我目前唯一见到的一个。这个工具是MIT做出来的，知道的人不多，是针对移动设备的网络经常断开设计的。

它的具体原理是，每次初始登录使用SSH，之后就不再使用SSH了，而是使用一个基于UDP的SSP协议，能够在网络断开重连的时候自动重新连接，所以从使用者的角度来看就像从来没有断开过一样。

接下来，**我以阿里云ECS主机、运行Ubuntu18.04为例，与你分享Mosh+Tumx的具体安装和设置方法。**

第一，服务器端安装并运行Mosh Server。

```
junge@iZ4i3zrhuhpdbhZ:~$ sudo apt-get install mosh

```

第二，打开服务器上的UDP端口60000~61000。

```
junge@iZ4i3zrhuhpdbhZ:~$ sudo ufw allow 60000:61000/udp

```

第三，在阿里云的Web界面上修改主机的安全组设置，允许UDP端口60000~61000。

<img src="https://static001.geekbang.org/resource/image/10/52/107e8927ab12b30699699db5ba54af52.jpg" alt="">

第四，在客户端（比如Mac上），安装Mosh Client。

```
jasonge@Juns-MacBook-Pro-2@l$ brew install mosh

```

第五，客户端使用Mosh，用与SSH一样的命令行连接到服务器。

```
jasonge@Juns-MacBook-Pro-2@l$ mosh junge@&lt;server-ip-or-name&gt;

```

下面这个录屏演示的是，我日常工作中使用Mosh + Tmux的流程。期间我会断开无线网，你可以看到Mosh自动连接上了，就好像来没有断过一样。

<img src="https://static001.geekbang.org/resource/image/9b/69/9beb7e70191ccd475d530aa4a5654a69.gif" alt="">

## 小结

今天，我与你分享的是使用命令行工具工作时，涉及的环境配置问题。

首先，我与你介绍了选择模拟终端、选择和配置Shell的重要准则，并结合案例给出了具体的工具和配置方法，其中涉及的工具包括iTerm2、Bash、Zsh、Fish等。然后，我结合远程SSH这一常见工作场景，给出了使用Tmux和Mosh的优化建议。

掌握了关于环境配置的这些内容以后，在下一篇文章中，我将与你介绍具体命令行工具的选择和使用。

其实，我推荐开发者多使用命令行工具，并不是因为它们看起来炫酷，而是它们确实可以帮助我们节省时间、提高个人的研发效能。而高效使用命令行工具的前提，是配置好环境。

以Mosh为例，我最近经常会使用iPad SSH到远端服务器做一些开发工作。在这种移动开发的场景下，iPad的网络经常断开，每次重新连接开销太大，基本上没办法工作。于是，我最终发现了Mosh，并针对开发场景进行了设置。现在，每次我重新打开iPad的终端时，远程连接自动恢复，好像网络从没有断开过一样。这样一来，我就可以在移动端高效地开发了。

而对研发团队来说，如果能够对命令行工作环境进行优化和统一，毫无疑问会节省个人选择和配置工具的时间，进而提升团队的研发效能。

## 思考题

你觉得Tmux和Screen的最大区别是什么？是否有什么场景，我们必须使用其中的一个吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！


