tmux 与 iTerm2 的集成

陪她去流浪 桃子 2022年12月15日 阅读次数:2601

本文简单介绍 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

参数 -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,不会有提示):

new-window new-tab

它问你,“你希望创建哪一种类型的新标签页?” 因为当前的窗口是 iTerm2 和 tmux 联手的窗口,所以这里有两个选择:

  • 创建 tmux 的窗口/标签页(高亮的)
  • 创建 iTerm2 的窗口/标签页

通常,我认为,既然我在使用 tmux,那就应该创建 tmux 的窗口/标签页,所以我选择第一项。 如果选择的是后者,那么打开的是 iTerm2 的窗口,这样就可以同时使用 tmux 和 iTerm2。

但注意,tmux 并没有所谓的“标签页”概念,但是为了与 iTerm2 对应,我猜测在实现上,tmux 内部有一个窗口组(Window Group)的概念: 同窗口组名的 tmux 窗口在 iTerm2 上显示为一个窗口内的标签组。不同的,就新开一个 iTerm2 窗口显示。不必过分追究,这些都是逻辑概念。

同样,在关闭窗口时,会有如下提示:

close

它问你,是:

  • 隐藏当前窗口?
  • 取消附加 tmux?(后续可 tmux a
  • 还是干掉当前窗口?

你可以自行决策。

特别地,隐藏窗口 会被放置到 tmux Dashboard 中。可以通过 “Shell → tmux → Dashboard” 菜单和面板进行管理,本文不再详述。

隐藏控制窗口

控制窗口多数时候没有显示的必要,可以在 iTerm2 的设置内将其隐藏(Bury)

bury

并且设置附加到已有 tmux 时的行为,即 tmux -CC a 时的行为,有三个选项:

  • Native Windows 以原生新窗口的方式打开
  • Native Tabs in a new window 在窗口的标签页中打开
  • Tabs in the attaching window 在当前窗口打开

我一般选择第三个,具体区别可以自行体验。

有新的 Bugs 吗?

目前还没太遇到。

最佳实践

一些我觉得不错的最佳实践。

创建或者附加

通常来说,一个服务器上只会开一个 tmux 会话(连接),对于这个会话,我们要么是想创建(如果不存在),要么是想附加上去(如果已经存在): 所以下面这个命令非常合适:

tmux -CC new -A -s main

或简写为:

tmux -CC new -Asmain

当然,我更为它们设置了别名:

alias ta='tmux new -Asmain'
alias tc='tmux -CC new -Asmain'
alias td='tmux detach'

可为服务器建立独立的 Profile

profile

建立好后,可以使用以下几种方式之一打开:

new1 new2 new3

最后一张图叫“Open Quickly(⇧⌘O)”。

参考

这篇文章的内容已被作者标记为“过时”/“需要更新”/“不具参考意义”。

标签:tmux · iTerm2