本文简单介绍 tmux 和 iTerm2 的用途、使用过程中遇到的困难及如何使用 tmux 与 iTerm2 集成来解决这些问题。 前面篇幅有点多,但本身的操作非常简单,可直接跳到最后。
都能干什么?
iTerm2 是苹果 macOS 上的一个原生图形界面终端模拟器,支持窗口(Window)、标签页(Tab)、窗格(Pane)。 tmux 是运行在 shell 内的命令行终端模拟器,同样也支持窗口(对应到 iTerm2 的标签页)、窗格。 这是它们的相同点。而它们的不同点在于:
- iTerm2 每打开一个窗口或窗格时,是运行本地的 shell 程序。如果原来登录了 ssh,则新打开的窗口或窗格不会登录 ssh;
- tmux 运行在 shell 内,当我们 ssh 到服务器后运行 tmux 时,其新建的窗口或者窗格也在这个 shell 内。就是说,在服务器上开多个窗口。
所以使用 tmux 带来的一个的好处是:窗口(和窗格)的管理跟随 shell。更简单地说,如果 tmux 运行在本地的 shell 内, 则是管理本地的一组窗口;如果 tmux 运行在 ssh 内,则管理的是服务器上的一组窗口。 再加上 tmux 本身基于 C/S 模型,分为客户端 tmux-client 和服务端 tmux-server。 客户端可以随时与服务端建立连接与断开连接并重连。 所以即便 ssh 意外断开,只需要重新登录 ssh 后打开 tmux-client 即可完全恢复断开之前的窗口状态。 这是 tmux 的杀手锏能力。(我曾在《tmux 入门教程》中简单介绍过 tmux 的使用,可以阅读一下。)
所以 tmux 能完全取代 iTerm2 的窗口管理工作吗?
差强人意。
当然,很多人这样做。即便是在本地,也完全使用 tmux 的窗口管理能力,而不是 iTerm2 的。 我曾一度也这样使用,尝试体验过几天,有几个堪称灾难的体验迫使放弃了这样做:
-
最反人类的应该是滚动了:滚动鼠标时不会滚动历史输出内容,而是滚动历史命令
这是因为,在默认配置下,没有开启鼠标消息支持,鼠标滚动事件此时是被翻译成按上、下键的。 更为悲壮的是,如果 shell 此时不是 readline 模式(比如简单执行
cat
的时候),将会看到大量^[[B
和[[A
。 甚至可能伴随着系统一顿 alert 告警声。吓退了当时的你没有?这应该是新手入门 tmux 最大的困惑吧? -
然后要属选择复制了:鼠标选择时会跨多个窗格选中内容,啥玩意儿啊!
虽然 tmux 在一个 shell 内模拟出了一个支持多窗口的命令行程序,但是 iTerm2 不认。 iTerm2 它只知道你 tmux 是一个普通的命令行程序。它不懂你窗口、窗格的概念是什么的。 这跟在 vim 内开启行号后在 iTerm2(其它终端模拟器也一样)内复制文本时行号也会被选中一个道理。
拷贝模式(copy-mode) 可以解决文本选区问题,但是会发现,拷贝的内容很可能不是在操作系统的剪贴板内, 所以没办法跨应用程序粘贴了。取而代之的是 tmux 自己的 buffer 内,并可在 tmux 内粘贴。
临时把窗格最大化(
tmux resize-pane -Z
)能缓解这个问题。尝试多复制一点内容,发现滚动有问题……它不滚。 -
窗格没法用鼠标切换
老手可以用快捷键来切换,新手需要一定的适应成本。
开启鼠标模式能缓解以上这两个问题吗?
能,但会带来新的问题。
鼠标模式可以通过 tmux set mouse
来开启。开启后:
-
历史内容可以滚动了,虽然不太流畅;
-
鼠标也能正确选中某个窗格内的文本了…
…但是复制的内容仍然可能不在操作系统的剪贴板内。 因为默认情况下,iTerm2 是不允许 shell 内的命令访问操作系统剪贴板的,很多人大概也没主动去开启;
另,vim 的复制(yank)也不支持复制到操作系统剪贴板内,除非编译时
+clipboard
了。但这玩意儿默认也没编译进去。 没几个人会手动编译自己电脑上的 vim 吧?更别说是服务器上的了。 -
能用鼠标点击切换窗格了;
等等……这不是正常该有的操作吗?
以上这些“问题”是我在使用过程中遇到的。当然到底是不是问题,见仁见智,毕竟每个人的使用习惯大相径庭。习惯是很难以改变的一种东西。
本身问题这么多,我们仿佛又在创造问题然后解决问题。使用 tmux 的人是不是有受虐倾向啊?🤔️ 想用就用吧,不想用就算了。
iTerm2 与 tmux 集成
单靠 tmux 自身的努力(比如支持鼠标操作,作者本身是不使用鼠标的,应该算是勉为其难地加上了?)没有完美地解决以上问题。 最后还是靠 tmux 和 iTerm2 的合作。这个所谓的合作,就是本文的主题: iTerm2 与 tmux 集成 —— 你可以继续使用 tmux,但是其窗口与窗格管理能力交给 iTerm2 来做。 强强联手,达到合二为一的效果。
一键集成
并没有太多设置,也不用装什么插件。唯一要做的就是,多加一个 tmux 运行时参数:
参数 -CC
使 tmux 进入控制模式(Control Mode)。
此时会新打开一个窗口,这个新打开的窗口就是 iTerm2 与 tmux “联手” 的窗口。原来的窗口起控制作用,不用操作。
控制模式下:tmux 会与 iTerm2 进行文本协议交换,iTerm2 可以发送命令给 tmux 来控制 tmux 的行为, 比如创建窗口、创建窗格、窗口大小改变等。iTerm2 也会接受来自 tmux 的响应以创建对应的窗口、对应的窗格来匹配 tmux 的行为。
如果你想体验一下这个协议,你可以只加一个 -C
参数,然后在另外一个 iTerm2 窗口里面 tmux a
附加到 -C
创建的这个
tmux 上,然后在 -C
这个窗口上发送 new-window
看看有没有创建新窗口。
另外,tmux -CC
可以在本地和服务器上执行,效果一样。
体验原生的窗口与窗格
在 -CC
后新打开的窗口中,你可以像原来使用 iTerm2 一样,开新标签页,拆分窗格,这些窗口与窗格对应到 tmux 的一个窗口与窗格。
最大的收益是,现在的窗口文本滚动、文本选区、查找、复制功能都变得很好使了,非常的原生。不再有上述提到的问题。
当开新的 iTerm2 窗口或标签页时,会收到 iTerm2 的如下提示(切分小窗格默认就是 tmux 的 Pane,不会有提示):
它问你,“你希望创建哪一种类型的新标签页?” 因为当前的窗口是 iTerm2 和 tmux 联手的窗口,所以这里有两个选择:
- 创建 tmux 的窗口/标签页(高亮的)
- 创建 iTerm2 的窗口/标签页
通常,我认为,既然我在使用 tmux,那就应该创建 tmux 的窗口/标签页,所以我选择第一项。 如果选择的是后者,那么打开的是 iTerm2 的窗口,这样就可以同时使用 tmux 和 iTerm2。
但注意,tmux 并没有所谓的“标签页”概念,但是为了与 iTerm2 对应,我猜测在实现上,tmux 内部有一个窗口组(Window Group)的概念: 同窗口组名的 tmux 窗口在 iTerm2 上显示为一个窗口内的标签组。不同的,就新开一个 iTerm2 窗口显示。不必过分追究,这些都是逻辑概念。
同样,在关闭窗口时,会有如下提示:
它问你,是:
- 隐藏当前窗口?
- 取消附加 tmux?(后续可
tmux a
) - 还是干掉当前窗口?
你可以自行决策。
特别地,隐藏窗口 会被放置到 tmux Dashboard 中。可以通过 “Shell → tmux → Dashboard” 菜单和面板进行管理,本文不再详述。
隐藏控制窗口
控制窗口多数时候没有显示的必要,可以在 iTerm2 的设置内将其隐藏(Bury):
并且设置附加到已有 tmux 时的行为,即 tmux -CC a
时的行为,有三个选项:
- Native Windows 以原生新窗口的方式打开
- Native Tabs in a new window 在窗口的标签页中打开
- Tabs in the attaching window 在当前窗口打开
我一般选择第三个,具体区别可以自行体验。
有新的 Bugs 吗?
目前还没太遇到。
最佳实践
一些我觉得不错的最佳实践。
创建或者附加
通常来说,一个服务器上只会开一个 tmux 会话(连接),对于这个会话,我们要么是想创建(如果不存在),要么是想附加上去(如果已经存在): 所以下面这个命令非常合适:
1
|
|
或简写为:
1
|
|
当然,我更为它们设置了别名:
1 2 3 |
|
可为服务器建立独立的 Profile
建立好后,可以使用以下几种方式之一打开:
最后一张图叫“Open Quickly(⇧⌘O)”。