tmux
tmux是一个终端复用工具,她可以在一个屏幕上创建、访问和控制多个终端。tmux 同样可以从屏幕上分离让终端在后台继续运行。
安装
几乎所有的发行版都存在不同版本的 tmux,使用对应的包管理工具安装即可。当然我们也可以从源码安装。
基本概念
tmux 是标准的 C/S 程序。tmux 的所有状态都保存在 tmux server 进程中,他在后台运行并管理 tmux 运行的所有其他进程。
当我们执行 tmux 命令时 tmux server 会自动启动,直到没有在运行的进程时 tmux server 会自动退出。当用户执行 tmux 命令时 tmux client 会接管终端并通过 tmux client 连接到 tmux server ,并使用 /tmp
中的套接字来与 tmux server 进行通信。
tmux 还包含三个核心的概念:
- session: 会话,每次执行 tmux 命令都会开启一个会话。同一个会话具有同样地执行环境
- windows: 窗口,一个会话中具有多个窗口。窗口会占据整个终端的空间
- panes: 面板,一个窗口可以包含多个面板,而组织面板的方式被称为
window layout
每个会话具有同样地执行环境,而窗口是同一个执行环境中的不同任务(例如编辑、运行等),而面板是同一个任务的不同分支。我们执行命令都是在面板层面进行的。第一次运行 tmux 会开启一个会话,这个会话只包含了一个窗口,而这个窗口只有一个面板。
Tips
每次操作都只能在一个面板上进行,该面板被称为 active pane
,active pane
所在的窗口被称为 current window
。上一个 current window
被称为 last window
。很多命令会引用这些术语
会话默认会以 0 1 2...
作为 session name
,但是他并不表示索引,会话之间是平等的关系。当然在创建会话是也能够指定名称。
窗口默认会根据创建的先后顺序创建 window index
(所以存在 pre/next 关系),窗口默认会以 active pane
中运行的程序名称作为 window name
(可以更改, 并且并不要求唯一)。
Tips
面板也有 index 和 title,其中 index 是以顺时针自动编号的(无法更改),切换面板经常会依赖这个编号,而 title 并不是很常用
基础使用步骤
tmux 的使用就是对 session、windows 和 panes 的增删改查。
create session
当用户执行 tmux 命令时就会创建一个会话,它实际上是一个语法糖,完整的命令应该是:
# tmux / tmux new 都可以
tmux new-session
# 可以使用 -s 来指定名称
# 如果不指定默认名称为 0, 1, 2 ...
tmux new-session -s session_name
创建一个新的会话之后,会自动连接到该会话并创建一个索引为 0 的窗口,该窗口包含一个面板,这个面板中运行了一个 shell。
status line
默认配置下,当我们连接到一个 session 后,会下方显示一个绿色的状态栏,其中包含了一些内容,典型定义:
# [0]: session name
# 0:man : window index:window name
# - : last window
# * : current window
# "MacBook-Pro" : pane title(默认是运行 tmux 的 hostname)
# 10:33 22-Feb-20 : time date
[0] 0:man 1:bash- 2:bash 3:bash* "MacBook-Pro" 10:33 22-Feb-20
Tips
tmux 默认风格出了名的丑,不过他提供了强大的配置方式
prefix key
一旦连接到 tmux client,输入的任何按键都会转发到 active pane
中运行的程序上(通常就是 bash 这样的 shell)。因此需要一个特殊的方式来与 tmux 本身进行交互,这就是 prefix key
的意义。
默认的 prefix key
是 <C-b>
。按下 prefix key
后 tmux 会等待另一个按键的按下,然后决定执行什么 tmux 命令。 要想发送 prefix key
本身可以按两次 prefix key
。
Tips
C-b ?
来查看所有 tmux 绑定的快捷键,在这个帮助面板中默认使用 <up> <down>
来滚动,q 来退出
command prompt
所有的快捷键实际上都映射到一个 tmux 命令上,不过命令必须在 shell 中被执行,如果 active pane
中运行的程序是一个 shell,那么完全可以使用命令来完成 tmux 操作,但是如果运行的时 python 这样的程序,就无法直接与 shell 交互了。
为了解决这个问题,tmux 可以使用 <prefix>:
打开一个交互式命令行(他会出现在状态栏所在的位置),在其中我们可以输入 tmux 命令。不过更多的时候都是使用快捷键操作。
在交互式命令行中不需要输入 tmux,例如在 shell 下创建 session 的命令为 tmux new-session -n mysession
而在交互命令行模式下直接输入 new-session -n mysession
即可。
Tips
可以使用 ; 来执行多个命令
copy mode
tmux 具有自己的 copy 和 paste 系统,它使用 copy mode
来实现,基本的操作方式如下:
- 输入
<prefix>[
进入复制模式 - 按下空格开始复制(begin-selection)
- 按下回车复制选中文本(copy-selection)并退出复制模式
- 按下
<prefix>]
粘贴文本
Tips
复制模式有两种按键形式,一种 emacs 风格,一种 vi 风格
tmux 复制操作的内容默认会存进 buffer 里面,我们可以使用 list-buffers
来查看其中的内容,buffer 最多能够保存 50 条复制信息,如果超过新的会覆盖旧的条目。
可以手动执行 delete-buffer -b <buffer-name>
来删除不需要的 buffer 来腾出空间,要想粘贴 buffer 的内容可以使用 parse-buffer
命令。
attach/detach/list/kill/rename session
当我们创建了一个 session,就可以在其中遨游了。除了创建 session 外对于 session 还有以下几个操作:
attach-session -t <session-name> -d
: 连接到一个 session, -d 指定断开其他会话<prefix>d
: 从一个 session 中分离,所谓的分离就是让 tmux 在后台执行,而连接就是重新连接到后台运行的 sessionlist-sessions
: 列出所有 session
tmux 默认会同步同一个会话的操作到所有会话连接的终端窗口中,这种同步机制,限制了窗口的大小为最小的会话连接。因此当你开一个大窗口去连接会话时,实际的窗口将自动调整为最小的那个会话连接的窗口,终端剩余的空间将填充排列整齐的小圆点,如果存在这种情况我们可以使用 attach-session -d
来断开其他该会话的连接。
分离和连接都不会影响在 session 中执行的任务,如果想要实现关闭当前的 session 有两种方式:
- 如果位于 shell 中,执行 exit 退出 shell 的同时关闭了该 session
- 可以执行
kill-session
命令来关闭当前 session
如果想要关闭所有的 session,使用 kill-server
。 默认 session 以整数序命名,我们可以使用 rename-session
来修改他们。
window
一个 session 中包含多个窗口,每一个时刻只有一个窗口被置入前端(有点类似标签页的概念)。对窗口的操作无法就是增删改查:
new-window -n <window-name> -t <index|target>
: 增加就是创建新窗口,参数都是可选的。-t
指定索引他默认是整数序,它决定了在状态栏的排序kill-window -a -t <index|target>
: 删除窗口,默认删除current window
,可以指定-t
来选择要删除的窗口,-a
会删除-t
指定外的所有窗口rename window
: window 的修改操作通常只有修改名称,这个是有意义的,我们的工作通常都是在不同 window 中进行的,重命名能够提示我们这个窗口在做什么select-window -l/-n/-p -t <window-index>
: 所谓的查找实际上就是选择激活的窗口,需要注意窗口是根据window-index
来索引的,其中-p(previous)、-n(next)
都是基于window-index
定位的,-l(last)
表示上一个current window
,我们可以使用-t
来切换到对应window-index
的窗口
pane
一个窗口可以被划分为多个面板,默认情况下窗口会包含一个占据整个窗口的面板。新建面板非常简单就是分割窗口(当然实际上也可以认为是分割面板)。面板是我们管理 tmux 的最小单元,我们运行的程序也都是在面板上进行的,对面板的操作同样类似增删改查:
split-window/split-pane -h/-v -d
: 分割窗口和分割面板是等价的,默认上下分割(即水平分割-v
), 可以指定参数来左右分割(即垂直分割-h
)。默认会自动激活新的 pane 可以指定-d
来避免这一点kill-pane -a -t <index|target>
: 删除面板,默认删除current window
,可以指定-t
来选择要删除的窗口,-a
会删除-t
指定外的所有窗口select-pane -D/-L/-R/-U -t <pane-index>
: 每一个时刻只能有一个active pane
,我们可以在 pane 间切换。-D(down)、-L(left)、-R(right)、-U(up)
能够在上下左右切换display-panes
: 显示面板编号
pane 还有一系列特殊的操作就是更改尺寸以及移动:
resize-pane -D/-L/-R/-U -Z
: 更改 pane 大小,其中-D(down)、-L(left)、-R(right)、-U(up)
就是在对应方向上增加和减小。-Z
能够让一个 pane 独占整个窗口,再次执行-Z
则会返回之前的视图swap-pane -U/-D -s <source-pane> -t <target-pane>
: 移动 pane,其中-D(down)、-U(up)
表示根据编号向前或先后移动。-s/-t
是需要同时定义的,一个指定源一个指定目标
resize-pane -Z
后会在状态栏上显示[window-index]:[window-name][Z]
其中的 Z 表示pane zoom
状态
choosing sessions,windows,panes and buffer
尽管 window 和 pane 提供了select-window/pane
命令来选择对应的视图,但是他们都很不直观,tmux 提供了一个 choose-tree 视图来让我们选择,视图是类似下面的内容:
(0) - 1: 6 windows (attached)
(1) ├─> 0: zsh-: "yangguodongdeMac-mini.local"
(2) ├─> - 1: [tmux]*Z
(3) │ ├─> 0: zsh: "yangguodongdeMac-mini.local"
(4) │ └─> 1: zsh*: "yangguodongdeMac-mini.local"
(5) ├─> 2: test_win: "yangguodongdeMac-mini.local"
(6) ├─> 8: zsh: "yangguodongdeMac-mini.local"
(7) ├─> + 10: zsh
(8) └─> 20: zsh: "yangguodongdeMac-mini.local"
(9) - 2: 1 windows
(M-a) └─> 0: zsh*: "yangguodongdeMac-mini.local"
(0-8)
: 编号, 按下对应的按键能够跳转并激活对应的组件1: 6 windows(attached)
: 代表 session 对应了[session-name]: [window-count] windows[(attached)](attached session)
0: [tmux]*: "yangguodongdeMac-mini.local"
: 代表 window ,对应了[+](display pane) [window-index]: [window-name]: [hostname] [*](current window)
1: zsh*: "yangguodongdeMac-mini.local"
: 代表了 pane,对应[pane-index]: [pane-title]: [hostname] [*](active pane)
根据打开的不同层次 tmux 提供了choose-client/session/window
他们实际上都是一样的,只不过展开的层次不一样而已。在这个面板下提供了类 vim 的快捷键(左右操作表示打开和关闭折叠),回车可以激活所选的组件,q 能够直接退出 choose 面板。
还有一个特殊的就是choose-buffer
可以列出所有的 buffer 的内容,我们可以选着来在active pane
中粘贴。
快捷键和命令
快捷键 | 命令 | 描述 |
---|---|---|
<prefix>? |
lsk -N | 查看所有按键绑定 |
<prefix>: |
- | 激活交互式命令行 |
- | new-session -n <session-name> |
创建 session |
<prefix>d |
detach | 分离 session |
- | list-sessions | 列出所有 session |
- | attach-session -t <session-name> |
连接到一个 session |
<prefix>$ |
rename-session | 重命名 session |
- | kill-session | 关闭当前 session |
- | kill-server | 关闭所有 session |
<prefix>c |
new-window -n <window-name> -t <index/target> |
创建 window,可以指定名称和索引(非必须) |
<prefix>0~9 |
select-window -t [0-9] |
切换到编号为 0-9 的 window |
<prefix>' |
select-window -t <window-index> |
打开命令行输入要去切换的window-index |
<prefix>n |
select-window -n | 切换到下一个 window |
<prefix>p |
select-window -p | 切换到上一个 window |
<prefix>l |
select-window -l | 切换到上一个 current window |
<prefix>, |
rename-window | 重命名 window |
<prefix>& |
kill-window | 删除当前 window |
<prefix>% |
split-pane -h | 垂直分割 pane |
<prefix>" |
split-pane -v | 水平分割 pane |
<prefix>Up/Left/Right/Down |
select-pane -U/-L/-R/-D | 激活上/下/左/右的 pane |
<prefix>q[0-9] |
select-pane -t [0-9] |
激活对应编号的 pane |
<prefix>q |
display-panes | 显示pane-index |
<prefix>x |
kill-pane | 删除当前 pane |
<prefix>{ |
swap-panes -D | 与下一个 pane 交换 |
<prefix>} |
swap-panes -U | 与上一个 pane 交换 |
<prefix>C-Up/C-Left/C-Right/C-Down |
resize-pane -U/-L/-R/-D | 更改 pane 大小 |
<prefix>z |
resize-pane -Z | 最大化 pane |
<prefix>s |
choose-session | 打开session choose 面板 |
<prefix>w |
choose-tree/choose-window | 打开window choose 面板 |
<prefix>[/] |
copy mode | 开启/关闭 copy mode |
配置
tmux 可以使用配置文件进行配置,默认的配置文件为$HOME/.tmux.conf
, 在 tmux 3.1 中遵循了 XDG 规范,允许$XDG_CONFIG_HOME/tmux/.tmux.conf
作为配置文件。
配置文件语法
tmux 配置文件非常简单,# 开头的为注释,每一行都是一个生效的配置。命令参数有三种表示形式:
- 字符串: 特点是空格必须被转义
- 使用
''
包括: 将~\ ~~
作为常规字符,不进行转义 - 使用
""
包括: 与字符串唯一的区别就是不需要转义空格
# 下面四个是等价的
set -g status-left "hello word"
set -g status-left "hello\ word"
set -g status-left 'hello word'
set -g status-left hello\ word
# 注意 '' 中不会转义, 下面是等价的
set -g status-left 'hello\ word'
set -g status-left "hello\\ word"
set -g status-left hello\\\ word
# ~ 作为特殊字符表示家目录,注意在 '' 中就是常规的 ~
# 可以设置环境变量, 他将被 tmux 的 pane 中使用
# 系统环境变量可以直接使用
MYFILE=myfile
source "~/$MYFILE"
按键绑定
使用bind-key
和unbind-key
命令可以更改 tmux 的按键绑定。每一个键位绑定都属于一个键位表,tmux 默认包含四个键位表:
- root: 该键位表下的按键不需要前缀键激活
- prefix: 核心的键位表,他们都需要使用前缀键来激活
- copy-mode: 只有激活 copy mode 并且是
emacs-style
下的按键绑定 - copy-mode-vi: 只有激活
copy mode
并且是vi-style
下的按键绑定
在进行按键绑定的时候,可以使用-T <key table>
来指定要绑定到的键位表,如果不指定默认位于 prefix 中:
bind-key -T prefix C-b send-prefix
bind-key -T prefix C-o rotate-window
bind-key -T copy-mode C-a send-keys -X start-of-line
unbind-key
用于解绑按键,这并不是必须的,bind-key
会覆盖掉他已经绑定的键位。unbind-key
更多的是为了统一风格。
复制模式
copy mode 下有一套独立命令以及键位映射方式:
# 绑定 copy-mode key-table
# 指定按键 <C-a>
# send-keys -X 可以认为是固定写法
# start-of-line 命令
bind-key -T copy-mode C-a send-keys -X start-of-line
命令 | emacs(1) | vi(1) | 描述 |
---|---|---|---|
begin-selection | C-Space | Space | 开始选择 |
cancel | q | q | 退出 copy mode |
clear-selection | C-g | Escape | 清空选着 |
copy-selection-and-cancel | M-w | Enter | 复制所选并退出 copy mode |
cursor-down | Down | j | 向下移动光标 |
cursor-left | Left | h | 向左移动光标 |
cursor-right | Right | l | 向右移动光标 |
cursor-up | Up | k | 向上移动光标 |
end-of-line | C-e | $ | 移动光标到行尾 |
next-word-end | M-f | e | 移动到下一个单词尾 |
start-of-line | C-a | 0 | 移动光标到行首 |
page-down | PageDown | C-f | Page down |
page-up | PageUp | C-b | Page up |
previous-word | M-b | b | 移动到上一个单词 |
options
tmux 内置了一系列的 options ,他们具有五个影响空间:
- server: 影响 tmux-server
- session: 影响 session
- window: 影响 window
- pane: 影响 pane
- user: tmux 并不使用但是会为用户保留
选项又分为全局和局部的,例如全局窗口选项会影响所有的窗口,而局部窗口选项只影响一个窗口。可以使用 show-options 来查看所有的选项,其中可以指定-s/-w
表示 session window 选项,使用-g
表示全局的。
要在配置文件中更改选项的值需要使用set [-g][-u] [option] [option-value]
的形式来设置。其中-g
表示设置全局选项,-u
表示取消设置(也就意味着恢复默认值)。options-value 有两种形式:
- 字符串: 如果是需要对应的值
off|on
: 如果是开关选项
运行脚本
可以直接在 tmux 配置中使用 run 来运行脚本:
个人配置
该配置的起点源于.tmux, 目前就做了键位的绑定。
# 可以使用 list-keys 来列出所有的键位映射
# 设置 C-b 为 prefix 这实际上是默认按键
set -g prefix C-b
unbind C-b
# 这一句确保可以向其他程序发送 C-b 键位,也就是连续按下两次 C-b
bind C-b send-prefix
# 设置默认的终端模式为 256 色模式
set -g default-terminal "screen-256color"
# 鼠标支持
set -g mouse on
# 窗口操作
## 分割窗口
bind s split-window -h -c "#{pane_current_path}"
bind S split-window -v -c "#{pane_current_path}"
bind r command-prompt -I "#W" { rename-window "%%" }
bind X confirm-before -p "kill-window #P? (y/n)" kill-window
bind C new-window -c "#{pane_current_path}" # 以当前面板路径来创建行窗口
# 面板
## 在面板之间移动
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
## 调整面板大小
### -r 表示可以重复按键,也就是说不需要在按下 prefix 可以连续切换
bind -r < resize-pane -L 5
bind -r + resize-pane -D 5
bind -r - resize-pane -U 5
bind -r > resize-pane -R 5
# copy-mode
## 开启 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-selection-and-cancel
## 打开 buffer 列表
bind b choose-buffer
# esc 响应时间,主要是 vim 中使用
set -s escape-time 0
按键绑定
按键/命令 | 说明 |
---|---|
<C-b> |
prefix |
<prefix>: |
打开交互式命令行 |
tmux n -t <session-name> |
创建 session |
tmux a -t <session-name> |
进入 session |
<prefix>d |
分离 session |
<prefix>c |
创建 window |
<prefix>C |
创建与当前窗口同路径的 window |
<prefix>0~9 |
切换到编号为 0~9 的 window |
<prefix>n/p |
切换到上一个、下一个 window |
<prefix>r |
重命名 window |
<prefix>X |
删除当前 window |
<prefix>s |
垂直分割 window |
<prefix>S |
水平分割 window |
<prefix>hjkl |
上下左右移动 active pane |
<prefix>x |
删除当前 pane |
<prefix><>-+ |
调整 pane 大小 |
<prefix>z |
最大化/还原 pane |
<prefix>w |
打开 session/window/pane 列表 |
<prefix>b |
打开 buffer 列表 |
<prefix>[ |
激活 copy mode |
<prefix>] |
粘贴 |
<copy mode>v |
开始选择 |
<copy mode>hjkl |
移动光标 |
<copy mode>y |
复制并退出 copy mode |
<copy mode>q |
直接退出 copy mode |
其他特殊设置
中文乱码
当我们在 tmux 中运行 nvim 时会发现不显示中文,则通常是没有设置 locale到 utf-8 导致的:
工作目录和 python 虚拟环境
当我们新建窗口或者面板都是进入 tmux 的工作目录(注意不是当前窗口的工作目录)。tmux 默认的工作目录是通过tmux a
进入 tmux 时所在的目录。如果我们想要根据当前窗口所在的工作目录来创建窗口或窗格需要指定-c
参数:
bind c new-window -c "#{pane_current_path}"
bind % split-window -h -c "#{pane_current_path}"
bind '"' split-window -v -c "#{pane_current_path}"
对于 python 虚拟环境是同样的道理,我们可以在进入 tmux 之前先激活 python 环境,之后进入 tmux 后所有新创建的窗口以及面板都会继承该环境(通常不会有 venv 这样的提示,但是 python 执行路径确实是虚拟环境)。因此我们可以用不同的 session 来管理不同的虚拟环境。
在Tmux split window and activate a python virtualenv中可能找到更优雅的方式
vim 颜色突变
这是由于终端类型导致的。shell 有一个环境变量$TERM
他表示终端类型,最初的终端都是物理工作台,他的值可能是hp2621
这样的具体物理显示器的型号。之后的终端通常都是虚拟终端,所以更多都是linux bsdos
这样的形式。
而当前$TERM
的经典值是xterm-256color
表示支持 256 色的终端。最初的终端都是单色的(显像管),之后发展到 16 色。而目前主流的终端都支持 256 色。tmux 中 vim 的突变就是由于 tmux 中的终端类型默认是 16 色的。我们可以通过echo $TERM
来查看。 xterm 算是最为古老的终端模拟器,因此这个名字被延续了下来。
因此要想在 tmux 中启用 256color 需要set -g default-terminal "screen-256color"
,tmux 中的终端被命名为 screen。