tmux学习笔记¶
约 2894 个字 127 行代码 预计阅读时间 22 分钟
tmux(terminal multiplexer,终端复用器)是服务器运维和日常开发中非常顺手的工具。它的核心价值概括起来有三点:
- 会话保活。ssh连上远程服务器,一旦网络抖动或者本地终端关闭,所有前台进程就跟着被SIGHUP了。把命令放在tmux里运行,ssh断了只是detach而已,进程继续在服务器上跑,下次登录再
tmux attach回来。 - 一屏多窗。把一个物理终端窗口分成多个pane,一边看日志一边敲命令一边跑htop,不用频繁切alt+tab。
- 多终端共享同一会话。在公司电脑attach一个session,回家在笔记本上再attach,两边能看到同样的屏幕,适合结对调试或者跨设备继续工作。
本文按「概念→会话→窗口→窗格→复制模式→配置→插件」的顺序展开,所有快捷键都以tmux默认的前缀键 Ctrl+B 为基础。后面会讲到怎么把前缀键改成更顺手的 Ctrl+A。
安装¶
大多数发行版仓库里都自带:
# Debian / Ubuntu
sudo apt install tmux
# CentOS / RHEL
sudo yum install tmux
# macOS(Homebrew)
brew install tmux
# Arch
sudo pacman -S tmux
装完以后检查版本:tmux -V。建议用 3.0 以上的版本,很多现代配置项(比如set -g mouse on统一开关鼠标、copy-mode-vi的send-keys -X语法)在老版本里要么没有要么不一样。如果仓库里的版本太老,可以从源码编译,依赖是libevent和ncurses。
核心概念:session、window、pane¶
tmux里有三层嵌套关系,理清楚了后面的快捷键就不会乱:
- Session(会话):一组窗口的集合,可以被命名。session是attach/detach的单位,也是「保活」的真正单元——只要tmux server没被杀,session里的进程就一直在跑。
- Window(窗口):一个session里可以有多个window,概念上类似浏览器的标签页。每个window占满整个终端屏幕,同一时刻只会显示一个。
- Pane(窗格):一个window内部又可以被切分成若干pane,每个pane是一个独立的shell进程。
它们的关系大致是:
tmux server
└── session "work"
├── window 0: editor
│ ├── pane 0: vim
│ └── pane 1: shell
├── window 1: logs
│ ├── pane 0: tail -f a.log
│ └── pane 1: tail -f b.log
└── window 2: htop
tmux server在第一次运行tmux命令时自动被拉起来,之后所有的session都挂在这一个server下。想确认server是否在跑:tmux ls,没有server的时候会报no server running on /tmp/tmux-1000/default。
会话管理¶
启动一个匿名session:
启动时直接起名字(强烈推荐,否则session名就是0、1、2这种数字,多了根本分不清):
离开session但保留后台运行(这是tmux最常用的操作):按 Ctrl+B 然后松开,再按 D(detach)。也可以在任意shell里执行tmux detach。
列出所有活着的session:
重新挂回最近的session:
-d参数很有用:在attach新的客户端时把原来连接那个session的其他客户端踢下线。适合「家里和公司两个终端attach同一个session结果屏幕大小互相掐架」的情形:
干掉一个session:
重命名当前session:Ctrl+B ++$++,然后输入新名字回车。
保活的本质
tmux保活能力的关键在于它的子进程是由tmux server(一个daemon)fork出来的,而不是由你那个ssh shell fork出来的。ssh一断,只有你的ssh shell收到SIGHUP而死掉,server和它名下所有的shell都跟这个事件无关。所以只有kill-server、机器重启、或者系统OOM把tmux进程干掉,session才会真的丢。
窗口(window)管理¶
以下快捷键都省略了前缀 Ctrl+B。也就是说,c这一项实际的按法是先按 Ctrl+B、再按 C:
| 按键 | 作用 |
|---|---|
c | create,新建一个window |
, | 重命名当前window |
& | 关闭当前window(会弹确认) |
n | next,切到下一个window |
p | previous,切到上一个window |
0~9 | 直接切到编号为0~9的window |
l | last,切回上一次停留的window |
w | 打开window选择列表,可以用方向键选 |
f | 按窗口标题搜索 |
. | 改window的编号 |
window的编号默认从0开始,这个挺反直觉,习惯后可以在.tmux.conf里改成从1开始(见下面的配置章节)。
窗格(pane)管理¶
切分¶
| 按键 | 作用 |
|---|---|
% | 把当前pane左右切开 |
" | 把当前pane上下切开 |
x | 关闭当前pane(会弹确认) |
! | 把当前pane拎出来变成一个独立的window |
可以在配置里重映射成 | 和 -,视觉上符合直觉(见下面配置)。
切换焦点¶
| 按键 | 作用 |
|---|---|
o | 顺次切到下一个pane |
; | 切回上一次停留的pane |
| 方向键 | 按方位切pane(上下左右) |
q | 显示每个pane的编号,按数字键跳过去 |
z | zoom,把当前pane临时放大到全屏,再按一次复原 |
q+数字这个组合在pane比较多的时候非常省事,不用一格一格跳。
调整尺寸¶
按住前缀键之后用方向键也能调大小,但只动一格太慢。更顺手的方法是进入「按住模式」,在.tmux.conf里把方向键resize设成可重复:
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
-r表示repeat,在默认500ms的重复时间内可以连着按H、J而不用重复前缀。
布局¶
Ctrl+B 按 空格 会在tmux预置的几种布局间循环切换:even-horizontal、even-vertical、main-horizontal、main-vertical、tiled。一屏多pane的时候用这个比手动调尺寸方便得多。
交换与移动¶
| 按键 | 作用 |
|---|---|
{ | 把当前pane往前挪一位 |
} | 把当前pane往后挪一位 |
Ctrl+o | 整个window里所有pane轮换一圈 |
跨window拉pane::join-pane -s <src>:<win>.<pane> -t <dst>:<win>,在命令模式下运行。如果只是简单把当前pane合并到另一个window,join-pane -t :<target-window>更省事。
复制模式(copy mode)¶
tmux有自己的滚屏和复制机制。直接在tmux里滚鼠标滚轮(没开mouse的话)什么都不会发生,因为你没有进入复制模式。
进入复制模式:Ctrl+B ++[++(左方括号)。进去之后屏幕右上角会出现[N/M]这样的位置提示,这时候就能用方向键、PgUp/PgDn滚动了。按 q 或者 Esc 退出。
默认的键位是emacs风格的,用vi风格在配置里加上:
vi模式下的操作:
| 按键 | 作用 |
|---|---|
h/j/k/l | 左下上右移动 |
w/b | 跳词 |
gg / G | 跳到最顶 / 最底 |
/ / ? | 向下 / 向上搜索 |
n / N | 下一个 / 上一个匹配 |
| Space | 开始选区 |
| Enter | 把选区拷到tmux buffer |
| Ctrl+B ++]++ | 把最近一次的buffer粘贴回来 |
这个粘贴仍然在tmux内部。想和系统剪贴板互通需要借助外部工具,Linux下一般是xclip或者xsel,macOS下是pbcopy/pbpaste,WSL下是clip.exe。以Linux为例:
这样按y复制时就会同时送到X11剪贴板,在浏览器里Ctrl+V粘贴即可。
命令模式¶
按 Ctrl+B ++:++ 进入命令模式,状态栏会变成冒号提示符,就像vim的 ex 命令。所有在.tmux.conf里能写的指令在这里都能临时执行,调试配置的时候特别方便。
一些常用的命令:
:new-window -n edit 新建window并命名
:rename-window logs 给当前window改名
:rename-session deploy 给当前session改名
:kill-pane 关闭当前pane
:source-file ~/.tmux.conf 重载配置(非常常用)
:set -g mouse on 临时开启鼠标支持
:swap-window -s 3 -t 0 把3号和0号window对调
:move-window -t 2 把当前window移到位置2
快捷键速查¶
把上面的东西整理成一张图,打印出来贴屏幕上(前缀键均为 Ctrl+B,省略):
| 类别 | 按键 | 说明 |
|---|---|---|
| session | d | detach |
| session | s | 选择session |
| session | $ | 重命名session |
| session | ( / ) | 上一/下一个session |
| window | c | 新建window |
| window | , | 重命名window |
| window | & | 关闭window |
| window | n / p | 下/上一个window |
| window | 0~9 | 跳到编号window |
| window | w | 选择window |
| window | f | 搜索window |
| pane | % / " | 左右/上下切分 |
| pane | 方向键 | 按方向切pane |
| pane | o / ; | 下一个/上一次pane |
| pane | q | 显示编号 |
| pane | x | 关闭pane |
| pane | z | 放大/还原pane |
| pane | { / } | 前后挪动pane |
| pane | 空格 | 切换布局 |
| 其他 | [ | 进入复制模式 |
| 其他 | ] | 粘贴 |
| 其他 | : | 命令模式 |
| 其他 | ? | 显示所有快捷键绑定 |
| 其他 | t | 在当前pane显示一个大时钟(闲着无聊可以看) |
按 ? 显示所有绑定是一个很容易被忽略的自救技能:忘记某个键的时候不用Google。
配置文件 .tmux.conf¶
tmux启动时会读 ~/.tmux.conf,修改后可以用 tmux source ~/.tmux.conf 或者在tmux里按 Ctrl+B ++:++ 执行 source-file ~/.tmux.conf 立即生效,不需要重启server。
下面是一个比较通用的配置模板,把上面的注意事项都合并进来了:
# -------- 基础行为 --------
# 可以前缀键改成 Ctrl+a,和 screen 一致
unbind C-b
set -g prefix C-a
bind C-a send-prefix # 按两次 Ctrl+a 向内层应用传一个 Ctrl+a
# 窗口和面板编号从 1 开始
set -g base-index 1
setw -g pane-base-index 1
# 有 window 关闭时,自动重编号,让编号保持连续
set -g renumber-windows on
# 历史行数调大一点,方便向上翻
set -g history-limit 50000
# 降低 escape 延迟,vim 里切 normal 模式会感觉明显顺
set -sg escape-time 10
# 支持真彩色(前提是外层终端也支持)
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",xterm-256color:Tc"
# 鼠标:允许滚轮、点选 pane、拖动调整大小
set -g mouse on
# -------- 直观的切分按键 --------
# | 是竖线,表示左右切;- 是横线,表示上下切
unbind '"'
unbind %
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
bind c new-window -c "#{pane_current_path}" # 新 window 也继承当前目录
# -------- 方向键 resize(可重复按) --------
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
# -------- vi 风格的复制模式 --------
setw -g mode-keys vi
bind -T copy-mode-vi v send-keys -X begin-selection
bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -selection clipboard -i"
# -------- 快速重载配置 --------
bind r source-file ~/.tmux.conf \; display-message "tmux.conf reloaded."
# -------- 状态栏 --------
set -g status-interval 5
set -g status-left-length 30
set -g status-right-length 60
set -g status-left "#[fg=green]#S #[fg=white]| "
set -g status-right "#[fg=cyan]%Y-%m-%d %H:%M "
配置里的 #{pane_current_path} 是tmux的格式变量之一,用来拿当前pane的cwd。结合new-window -c、split-window -c用以后,开新pane直接就在当前目录里,不用再cd一遍。
插件管理:tpm + resurrect + continuum¶
tmux的插件生态靠 tpm(tmux plugin manager) 撑起来。装它:
然后在.tmux.conf末尾追加:
# 插件列表
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
# resurrect:保存更多内容(vim session、pane 内容等)
set -g @resurrect-capture-pane-contents 'on'
set -g @resurrect-strategy-vim 'session'
# continuum:开机自动恢复 + 每 15 分钟保存一次
set -g @continuum-restore 'on'
set -g @continuum-save-interval '15'
# 这一行必须放在文件最后
run '~/.tmux/plugins/tpm/tpm'
装好以后在tmux里按 Ctrl+A ++I++(大写,前提是已经把前缀键改成了C-a)安装所有插件。U 是升级,alt+u 是卸载不在列表里的插件。
两个最值得装的插件:
- tmux-resurrect:手动保存/恢复 session。按 Ctrl+A Ctrl+S 存一次,按 Ctrl+A Ctrl+R 恢复。它会把所有window、pane的布局,正在跑的程序(可配置),甚至vim的session都保存下来。服务器重启后session原样回来,体验近乎魔法。
- tmux-continuum:resurrect 的自动化层,定期调用resurrect保存快照,并且在tmux server启动时自动恢复最近一次的快照。两个插件一起用,就有了「关掉不慌」的底气。
一些坑和细节¶
- 嵌套tmux。本地开了个tmux再ssh进远端又开一个tmux,这时候两个tmux都抢前缀键 Ctrl+A。一个简单的绕开办法是连按两次前缀键:
send-prefix会把第二个 Ctrl+A 原样送给里层tmux,里层就收到前缀了。另一种做法是给里层tmux换一个前缀键。 - tmux杀不掉僵死的client。偶尔会遇到ssh断线但tmux没检测到,再attach的时候屏幕尺寸卡在老的尺寸,显示发虚。这时候在新attach的session里按 Ctrl+A ++D++(大写,detach-client子命令)挑一个客户端踢掉;或者attach的时候就加
-d强制踢别人。 - 复制出来是乱码。多半是没开
utf8/locale 错了。确认echo $LC_ALL $LANG是UTF-8的,并且terminal emulator本身支持Unicode。 - 颜色不对。
$TERM在tmux内部会被设置成screen-256color或tmux-256color。有些程序(比如vim的color scheme)在这两种下表现不一样。最稳的做法是按上面配置里那样显式设置default-terminal并加上terminal-overrides的Tc来开启真彩色。 set -gvssetw -g。set管全局/server级别选项,setw(set-window-option的缩写)管窗口级别选项。大多数时候两者都能用-g加上,但涉及到window-specific的项(比如mode-keys、window-status-*)一定要用setw。-
在脚本里启动tmux。
tmux new -d -s work 'long-command.sh'能以detach状态启动一个只跑某条命令的session,命令执行完session就退出。想让命令退出之后仍保留一个shell,可以在命令后面加; bash或者起一个空session再用send-keys发命令:C-m就是回车。send-keys能做的事情很多,适合写一些自动布局的启动脚本。
一个典型的日常工作流¶
最后以一个常见的工作流串一下前面的知识点。假设要在一台远程服务器上跑一个很久的实验:
ssh server
tmux new -s exp # 起一个叫 exp 的 session
# 窗口 1:跑实验
./run-experiment.sh
# 按 Ctrl+b c 新开一个窗口
nvidia-smi -l 2 # 这个 window 看 GPU
# 按 Ctrl+b c 再新开一个
tail -f logs/train.log # 这个看日志
# 不想看了就 Ctrl+b d 离开
然后本地关掉ssh窗口,去吃饭。回来重新登录:
上面所有的window和pane原封不动出现在眼前,三个视角切过去都还在跑。等实验跑完,按 Ctrl+B ++&++ 一个个关掉window,或者直接tmux kill-session -t exp一键清理。
用熟以后你会发现,服务器上几乎所有的事情都可以放在tmux里做——因为没有什么理由不这么做。