﻿---
title: "tmux 入门与配置指南"
description: "按实际使用中依次遇到的问题，一步步把 tmux 用起来：客户端 / 服务端架构如何让程序长时间运行，Session、Window、Pane 如何组织多任务，前缀键为什么要改、改了又会遇到什么，最后给出一份兼容 tmux 2.9+ 的完整配置。"
pubDate: 2026-03-22
updatedDate: 2026-10-18
tags: ["工具", "终端", "Linux"]
hero: "./hero.svg"
refs:
  - id: "opstool"
    title: "tmux 终端复用配置"
    url: "https://www.opstool.com/#blog/article/253"
    collect: "opstool-253"
    site: "opstool.com"
    note: "配置Tmux 一节"
  - id: "man"
    title: "tmux(1) 手册"
    url: "https://man7.org/linux/man-pages/man1/tmux.1.html"
    collect: "tmux-man-key-bindings"
    note: "KEY BINDINGS 一节"
  - id: "changes"
    title: "tmux CHANGES"
    url: "https://github.com/tmux/tmux/blob/master/CHANGES"
    collect: "tmux-changes-2-9"
    note: "CHANGES FROM 2.8 TO 2.9"
---

在服务器上干活久了，几乎每个人都会踩到同一个坑：一个跑了半小时的任务，因为网络抖了一下，SSH 断开，任务也跟着没了。tmux 就是为解决这类问题而生的工具，但它能做的远不止“断线不丢进程”。

本文不按功能清单来介绍 tmux，而是按照我们在实际使用中依次遇到的问题来展开：每一步先看现象，再分析原因，最后给出解决办法。读到最后，你会得到一份完整的 `~/.tmux.conf`，并且知道里面每一行是为了解决什么问题。

---

## 一、程序的长时间运行

### 现象

我们登录到服务器，执行一个耗时的数据迁移脚本：

```bash
$ ssh dev@10.0.0.8
$ ./migrate.sh
```

脚本跑到一半，笔记本合上了，或者 Wi-Fi 切换了一下。重新连上服务器，`ps` 一查，`migrate.sh` 已经不在了。

### 原因

SSH 断开时，sshd 会关闭这次登录对应的伪终端。内核随即给这个终端上的 shell 发送 `SIGHUP`（挂断信号），shell 退出前又会把 `SIGHUP` 转发给它启动的所有任务。大多数程序收到 `SIGHUP` 的默认行为就是退出，于是 `migrate.sh` 跟着一起没了。

换句话说，**程序的生命周期被绑在了这条 SSH 连接上**。

### 第一次尝试：nohup

最直接的办法是让程序忽略 `SIGHUP`：

```bash
$ nohup ./migrate.sh &
```

这样确实能让程序活下来，但很快就会发现新的问题：

- 输出被重定向到了 `nohup.out`，只能 `tail -f` 去看，没法再和程序交互。如果脚本中途要你输入一个 `y/n`，就只能干等着。
- 像 `vim`、`top`、交互式的数据库客户端这类需要终端的程序，根本没法用 `nohup` 放到后台。

所以我们真正需要的，不是让某一个程序“忽略挂断”，而是**让整个终端不随 SSH 断开**，下次连回来还能接着操作。

### 解决：tmux 的客户端 / 服务端架构

tmux（Terminal Multiplexer，终端复用器）正是这么做的。它把“终端”拆成了两部分：

- **tmux 服务端**：一个常驻后台的进程，真正托管着 shell 和你运行的程序。它不属于任何一次 SSH 登录，所以 SSH 断开时它不会收到 `SIGHUP`。
- **tmux 客户端**：你在 SSH 里看到的那个界面。它只负责把你的按键发给服务端、把服务端的画面显示出来，两者通过一个 Unix socket（默认在 `/tmp/tmux-<UID>/default`）通信。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tmux-arch-title tmux-arch-desc">
<title id="tmux-arch-title">tmux 的客户端 / 服务端架构</title>
<desc id="tmux-arch-desc">本地终端 SSH 到远程服务器并执行 tmux a -t work；tmux 客户端经 Unix socket 连到常驻的 tmux 服务端，服务端记录着 work 和 ops 两个会话，并托管 work 里的 vim、npm、pytest 进程；客户端随 SSH 断开退出，服务端和进程继续运行。</desc>
<defs>
<marker id="tmux-arch-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="200" height="368" rx="8"/>
<text class="t-zone" x="36" y="44">本地电脑</text>
<rect class="dd-zone" x="232" y="16" width="552" height="368" rx="8"/>
<text class="t-zone" x="252" y="44">远程服务器 · dev@10.0.0.8</text>
<path class="dd-edge" d="M200 128 H246" marker-end="url(#tmux-arch-arrow)"/>
<text class="t-note" x="224" y="120" text-anchor="middle">ssh</text>
<path class="dd-edge" d="M336 160 V206" marker-end="url(#tmux-arch-arrow)"/>
<text class="t-note" x="348" y="188">Unix socket</text>
<path class="dd-edge" d="M568 279 H598" marker-end="url(#tmux-arch-arrow)"/>
<path class="dd-edge" d="M580 279 V241 Q580 233 588 233 H598" marker-end="url(#tmux-arch-arrow)"/>
<path class="dd-edge" d="M580 279 V317 Q580 325 588 325 H598" marker-end="url(#tmux-arch-arrow)"/>
<rect class="dd-card" x="32" y="72" width="168" height="88" rx="6"/>
<path class="dd-card-head" d="M32.6 104 V78 Q32.6 72.6 38 72.6 H194 Q199.4 72.6 199.4 78 V104 Z"/>
<line class="dd-edge" x1="32" y1="104" x2="200" y2="104"/>
<text class="t-name" x="48" y="93">终端</text>
<text class="t-note" x="184" y="93" text-anchor="end">iTerm2</text>
<text class="t-mono" x="48" y="128">$ ssh dev@10.0.0.8</text>
<text class="t-mono" x="48" y="148">$ tmux a -t <tspan class="t-acc">work</tspan></text>
<rect class="dd-derived" x="248" y="72" width="176" height="88" rx="6"/>
<text class="t-name" x="264" y="98">tmux 客户端</text>
<text class="t-val" x="264" y="124">pid 3412</text>
<text class="t-val" x="264" y="144">tty /dev/pts/3</text>
<text class="t-say" x="444" y="96">虚线：客户端只活在这次 SSH 连接里。</text>
<text class="t-say" x="444" y="116">断开或 detach 时退出的只有它，</text>
<text class="t-say" x="444" y="136">服务端和右下的进程照常运行。</text>
<rect class="dd-card" x="248" y="208" width="320" height="144" rx="6"/>
<path class="dd-card-head" d="M248.6 240 V214 Q248.6 208.6 254 208.6 H562 Q567.4 208.6 567.4 214 V240 Z"/>
<line class="dd-edge" x1="248" y1="240" x2="568" y2="240"/>
<text class="t-name" x="264" y="229">tmux 服务端</text>
<text class="t-val" x="552" y="229" text-anchor="end">pid 2187</text>
<line class="dd-hair" x1="264" y1="266" x2="552" y2="266"/>
<line class="dd-hair" x1="264" y1="292" x2="552" y2="292"/>
<line class="dd-hair" x1="264" y1="318" x2="552" y2="318"/>
<text class="t-mono" x="264" y="257">socket</text>
<text class="t-val" x="336" y="257">/tmp/tmux-1000/default</text>
<text class="t-mono" x="264" y="283">session</text>
<text class="t-val" x="336" y="283"><tspan class="t-acc">work</tspan> · 2 windows</text>
<text class="t-note" x="552" y="283" text-anchor="end">attached</text>
<text class="t-mono" x="264" y="309">session</text>
<text class="t-val" x="336" y="309">ops · 1 window</text>
<text class="t-note" x="552" y="309" text-anchor="end">detached</text>
<text class="t-mono" x="264" y="335">client</text>
<text class="t-val" x="336" y="335">/dev/pts/3 → <tspan class="t-acc">work</tspan></text>
<rect class="dd-card" x="600" y="215" width="168" height="36" rx="6"/>
<text class="t-mono" x="612" y="238">vim main.go</text>
<text class="t-note" x="756" y="238" text-anchor="end">2201</text>
<rect class="dd-card" x="600" y="261" width="168" height="36" rx="6"/>
<text class="t-mono" x="612" y="284">npm run dev</text>
<text class="t-note" x="756" y="284" text-anchor="end">2214</text>
<rect class="dd-card" x="600" y="307" width="168" height="36" rx="6"/>
<text class="t-mono" x="612" y="330">pytest -f</text>
<text class="t-note" x="756" y="330" text-anchor="end">2230</text>
<text class="t-note" x="600" y="368">work 里各面板的进程和 pid</text>
</svg>
</div>
<figcaption>图 1 · 程序挂在服务端上，客户端只是一扇随时可以关上的窗</figcaption>
</figure>

SSH 断开时，退出的只有客户端，服务端和它托管的进程照常运行。用起来就是这样：

```bash
$ tmux new -s work          # 新建一个叫 work 的会话，并进入
$ ./migrate.sh              # 在 tmux 里跑任务
# 按 Ctrl+b 再按 d，主动离开（detach）；或者干脆断网
$ tmux ls                   # 重新登录后，看看服务端还托管着哪些会话
work: 1 windows (created Mon Oct 13 22:41:07 2026)
$ tmux attach -t work       # 连回 work，脚本还在原地跑着
```

断线不丢进程这个问题，到这里就解决了。

---

## 二、多任务的组织

### 现象

解决了断线问题之后，我们开始把越来越多的工作放到服务器上：一个标签开 `vim` 改代码，一个标签跑 `npm run dev`，再开一个看 `pytest` 的结果。每次断线，三个标签都要重新登录、重新 `cd` 到项目目录、重新把程序拉起来。

就算每个标签里都开一个 tmux 会话，也只是从“重新启动程序”变成了“重新 attach 三次”，并没有轻松多少。

### 分析

问题在于，我们用“SSH 连接”这个粒度来组织工作，而它太粗了：一次登录只对应一个终端画面。我们需要的是在**一次连接里**同时摆开多个终端，并且按项目把它们归拢在一起，断线后一条命令就能整体恢复。

### 解决：Session、Window、Pane 三层结构

tmux 服务端里的东西是分三层组织的，从外到内依次是：

- **Session（会话）**：一组相关工作的集合，通常一个项目一个，可以命名。attach 和 detach 都是以 Session 为单位的。
- **Window（窗口）**：相当于浏览器的标签页，一个 Session 里可以有多个 Window，同一时刻屏幕上只显示其中一个。
- **Pane（面板）**：一个 Window 可以横向或纵向切成多个 Pane，每个 Pane 都是一个独立的终端。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tmux-nested-title tmux-nested-desc">
<title id="tmux-nested-title">Session、Window、Pane 的嵌套关系</title>
<desc id="tmux-nested-desc">左边是屏幕上看到的 work 会话的 0 号窗口 editor，被切成运行 vim、zsh、pytest 的三个面板 %0、%1、%2，底部状态栏标出当前窗口 0:editor；右边是 tmux 记录的结构：会话 work 包含窗口 0:editor 和不在屏幕上的窗口 1:server，后者只有一个运行 npm run dev 的面板 %3。</desc>
<defs>
<marker id="tmux-nested-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="472" height="368" rx="8"/>
<text class="t-zone" x="36" y="44">屏幕上看到的</text>
<rect class="dd-zone" x="504" y="16" width="280" height="368" rx="8"/>
<text class="t-zone" x="524" y="44">tmux 记录的结构</text>
<rect class="dd-card" x="32" y="64" width="440" height="296" rx="6"/>
<path class="dd-card-head" d="M32.6 332 V354 Q32.6 359.4 38 359.4 H466 Q471.4 359.4 471.4 354 V332 Z"/>
<line class="dd-edge" x1="32" y1="332" x2="472" y2="332"/>
<line class="dd-edge" x1="272" y1="64" x2="272" y2="332"/>
<line class="dd-edge" x1="272" y1="198" x2="472" y2="198"/>
<text class="t-note" x="260" y="84" text-anchor="end">%0</text>
<text class="t-note" x="460" y="84" text-anchor="end">%1</text>
<text class="t-note" x="460" y="218" text-anchor="end">%2</text>
<text class="t-small" x="44" y="88">package main</text>
<text class="t-small" x="44" y="124">func main() {</text>
<text class="t-small" x="72" y="142">serve(":8080")</text>
<text class="t-small" x="44" y="160">}</text>
<text class="t-note" x="44" y="178">~</text>
<text class="t-note" x="44" y="196">~</text>
<text class="t-note" x="44" y="214">~</text>
<text class="t-small" x="44" y="320">"main.go" 5L, 52B</text>
<text class="t-small" x="284" y="88">$ git status</text>
<text class="t-small" x="284" y="106">On branch main</text>
<text class="t-small" x="284" y="124">nothing to commit</text>
<text class="t-small" x="284" y="142">$</text>
<text class="t-small" x="284" y="222">$ pytest -f</text>
<text class="t-small" x="284" y="240">12 passed in 0.81s</text>
<text class="t-small" x="284" y="258">waiting for changes…</text>
<text class="t-small" x="44" y="350">[work] <tspan class="t-acc">0:editor*</tspan> 1:server</text>
<text class="t-small" x="460" y="350" text-anchor="end">23:00 13-Oct-26</text>
<rect class="dd-card" x="520" y="64" width="248" height="264" rx="6"/>
<path class="dd-card-head" d="M520.6 96 V70 Q520.6 64.6 526 64.6 H762 Q767.4 64.6 767.4 70 V96 Z"/>
<line class="dd-edge" x1="520" y1="96" x2="768" y2="96"/>
<text class="t-name" x="536" y="85">Session</text>
<text class="t-mono" x="752" y="85" text-anchor="end">work</text>
<path class="dd-edge-acc" d="M472 168 H534" marker-end="url(#tmux-nested-arrow-acc)"/>
<rect class="dd-card" x="536" y="112" width="216" height="112" rx="6"/>
<text class="t-name" x="552" y="134">Window</text>
<text class="t-mono t-acc" x="736" y="134" text-anchor="end">0:editor</text>
<line class="dd-hair" x1="552" y1="144" x2="736" y2="144"/>
<text class="t-val" x="552" y="166"><tspan class="t-note">%0</tspan>  vim main.go</text>
<text class="t-val" x="552" y="188"><tspan class="t-note">%1</tspan>  zsh</text>
<text class="t-val" x="552" y="210"><tspan class="t-note">%2</tspan>  pytest -f</text>
<rect class="dd-derived" x="536" y="240" width="216" height="72" rx="6"/>
<text class="t-name" x="552" y="262">Window</text>
<text class="t-mono" x="736" y="262" text-anchor="end">1:server</text>
<line class="dd-hair" x1="552" y1="272" x2="736" y2="272"/>
<text class="t-val" x="552" y="294"><tspan class="t-note">%3</tspan>  npm run dev</text>
<text class="t-say" x="520" y="356">虚线：窗口存在，只是现在不在屏幕上</text>
</svg>
</div>
<figcaption>图 2 · 屏幕只显示会话里的一个窗口，其余窗口和面板照样在后台运行</figcaption>
</figure>

于是上面三个 SSH 标签的场景，就变成了一个 `work` 会话：`0:editor` 窗口里左边 `vim`、右边上下两个 Pane 分别是 shell 和 `pytest`，`1:server` 窗口里跑着 `npm run dev`。断线后只需要一句 `tmux attach -t work`，所有窗口和面板原样回来。

在 tmux 里操作窗口和面板，都要先按前缀键（默认 `Ctrl+b`），再按命令键。几个最常用的默认快捷键：

| 操作 | 默认快捷键 |
|---|---|
| 新建窗口 | `Ctrl+b c` |
| 切换到下一个窗口 | `Ctrl+b n` |
| 列出窗口并选择 | `Ctrl+b w` |
| 左右切分面板 | `Ctrl+b %` |
| 上下切分面板 | `Ctrl+b "` |
| 在面板间移动 | `Ctrl+b 方向键` |
| 离开会话（detach） | `Ctrl+b d` |

会话本身则在 tmux 外面用命令管理：

```bash
tmux new -s work            # 新建名为 work 的会话
tmux ls                     # 列出所有会话
tmux attach -t work         # 重新连接 work 会话
tmux kill-session -t work   # 删除 work 会话
```

---

## 三、快捷键的定制

### 现象

真正每天用起来，默认快捷键的问题就暴露出来了：

- `Ctrl+b` 两个键离得很远，一只手按起来很别扭，而几乎每个操作都要先按它。
- 左右切分是 `%`，上下切分是 `"`，和“切分方向”没有任何联系，而且都要再按 Shift。
- 在面板间移动要把手挪到方向键上，对习惯 vim 的人来说很打断节奏。

### 第一次尝试：把前缀键改成 Ctrl+a

`Ctrl+a` 两个键挨在一起，按起来顺手得多（这也是 GNU screen 的默认前缀）。切分快捷键则换成形状上更直观的 `\` 和 `-`，面板移动换成 vim 风格的 `h/j/k/l`[^opstool]：

```bash
unbind C-b
set -g prefix C-a          # 前缀键改为 Ctrl+a

bind '\' split-window -h   # 左右切分（反斜杠要加引号）
bind -   split-window -v   # 上下切分

bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
```

改完之后确实顺手了，但用了没多久，又冒出一个新问题：**在 shell 里按 `Ctrl+a` 跳回行首，没反应了**。

### 分析：前缀键其实是在切换按键表

为什么会这样？要理解 tmux 是怎么处理按键的。tmux 内部有多张按键表（key table），平时生效的是 `root` 表[^man]。`root` 表里最关键的绑定就是前缀键，它的作用是“把下一个键交给 `prefix` 表去查”；其他所有键都不拦截，原样交给当前 Pane 里的程序。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tmux-prefix-title tmux-prefix-desc">
<title id="tmux-prefix-title">前缀键是怎么工作的</title>
<desc id="tmux-prefix-desc">tmux 用两张按键表解释按键：root 表里只有 C-a 被绑定为切换到 prefix 表，其他键原样交给当前面板；prefix 表里 \ 左右切分、- 上下切分、a 把 Ctrl+a 原样发给程序、d 断开客户端。图中三组按键 l s 回车、C-a \、C-a a 分别得到 zsh 收到 ls、左右切出新面板、zsh 收到 Ctrl+a。</desc>
<defs>
<marker id="tmux-prefix-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="tmux-prefix-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="200" height="368" rx="8"/>
<text class="t-zone" x="36" y="44">你按下的键</text>
<rect class="dd-zone" x="232" y="16" width="352" height="368" rx="8"/>
<text class="t-zone" x="252" y="44">tmux 的按键表</text>
<rect class="dd-zone" x="600" y="16" width="184" height="368" rx="8"/>
<text class="t-zone" x="620" y="44">结果</text>
<path class="dd-edge" d="M124 143 H246" marker-end="url(#tmux-prefix-arrow)"/>
<path class="dd-edge" d="M112 253 H246" marker-end="url(#tmux-prefix-arrow)"/>
<path class="dd-edge" d="M112 305 H246" marker-end="url(#tmux-prefix-arrow)"/>
<path class="dd-edge" d="M568 143 H614" marker-end="url(#tmux-prefix-arrow)"/>
<path class="dd-edge" d="M568 253 H614" marker-end="url(#tmux-prefix-arrow)"/>
<path class="dd-edge" d="M568 305 H614" marker-end="url(#tmux-prefix-arrow)"/>
<path class="dd-edge-acc" d="M536 160 V206" marker-end="url(#tmux-prefix-arrow-acc)"/>
<text class="t-note" x="526" y="188" text-anchor="end">下一个键查这张表</text>
<rect class="dd-key" x="36" y="131" width="24" height="24" rx="4"/>
<text class="t-mono" x="48" y="147" text-anchor="middle">l</text>
<rect class="dd-key" x="64" y="131" width="24" height="24" rx="4"/>
<text class="t-mono" x="76" y="147" text-anchor="middle">s</text>
<rect class="dd-key" x="92" y="131" width="24" height="24" rx="4"/>
<text class="t-mono" x="104" y="147" text-anchor="middle">⏎</text>
<rect class="dd-key" x="36" y="241" width="40" height="24" rx="4"/>
<text class="t-mono t-acc" x="56" y="257" text-anchor="middle">C-a</text>
<rect class="dd-key" x="80" y="241" width="24" height="24" rx="4"/>
<text class="t-mono" x="92" y="257" text-anchor="middle">\</text>
<rect class="dd-key" x="36" y="293" width="40" height="24" rx="4"/>
<text class="t-mono t-acc" x="56" y="309" text-anchor="middle">C-a</text>
<rect class="dd-key" x="80" y="293" width="24" height="24" rx="4"/>
<text class="t-mono" x="92" y="309" text-anchor="middle">a</text>
<text class="t-say" x="36" y="348">C-a 自己不做事，只把</text>
<text class="t-say" x="36" y="366">下一个键交给 prefix 表</text>
<rect class="dd-card" x="248" y="72" width="320" height="88" rx="6"/>
<path class="dd-card-head" d="M248.6 104 V78 Q248.6 72.6 254 72.6 H562 Q567.4 72.6 567.4 78 V104 Z"/>
<line class="dd-edge" x1="248" y1="104" x2="568" y2="104"/>
<text class="t-name" x="264" y="93">key table</text>
<text class="t-mono" x="552" y="93" text-anchor="end">root</text>
<line class="dd-hair" x1="264" y1="130" x2="552" y2="130"/>
<text class="t-mono t-acc" x="264" y="121">C-a</text>
<text class="t-val" x="328" y="121">switch-client -T prefix</text>
<text class="t-note" x="264" y="147">其他键</text>
<text class="t-val" x="328" y="147">不拦截，交给当前 Pane</text>
<rect class="dd-card" x="248" y="208" width="320" height="140" rx="6"/>
<path class="dd-card-head" d="M248.6 240 V214 Q248.6 208.6 254 208.6 H562 Q567.4 208.6 567.4 214 V240 Z"/>
<line class="dd-edge" x1="248" y1="240" x2="568" y2="240"/>
<text class="t-name" x="264" y="229">key table</text>
<text class="t-mono t-acc" x="552" y="229" text-anchor="end">prefix</text>
<line class="dd-hair" x1="264" y1="266" x2="552" y2="266"/>
<line class="dd-hair" x1="264" y1="292" x2="552" y2="292"/>
<line class="dd-hair" x1="264" y1="318" x2="552" y2="318"/>
<text class="t-mono" x="264" y="257">\</text>
<text class="t-val" x="328" y="257">split-window -h</text>
<text class="t-mono" x="264" y="283">-</text>
<text class="t-val" x="328" y="283">split-window -v</text>
<text class="t-mono" x="264" y="309">a</text>
<text class="t-val" x="328" y="309">send-prefix</text>
<text class="t-mono" x="264" y="335">d</text>
<text class="t-val" x="328" y="335">detach-client</text>
<rect class="dd-card" x="616" y="125" width="152" height="36" rx="6"/>
<text class="t-name" x="632" y="148">zsh 收到 <tspan class="t-mono">ls⏎</tspan></text>
<rect class="dd-card" x="616" y="235" width="152" height="36" rx="6"/>
<text class="t-name" x="632" y="258">左右切出新 Pane</text>
<rect class="dd-card" x="616" y="287" width="152" height="36" rx="6"/>
<text class="t-name" x="632" y="310">zsh 收到 <tspan class="t-mono">Ctrl+a</tspan></text>
</svg>
</div>
<figcaption>图 3 · 前缀键只是切换按键表：C-a 之后的那个键才决定 tmux 做什么</figcaption>
</figure>

所以，把前缀键改成 `Ctrl+a` 之后，`Ctrl+a` 就在 `root` 表里被 tmux 拦截了，再也到不了 bash。bash 的“行首跳转”自然就失效了。

### 解决：send-prefix

既然 `Ctrl+a` 被 tmux 占用了，那就在 `prefix` 表里留一个出口，把它原样发给程序：

```bash
bind a send-prefix         # Ctrl+a a：把 Ctrl+a 发给当前 Pane
```

这样，需要行首跳转的时候，连按 `Ctrl+a a` 就行了。这个代价换来的是每天成百上千次更顺手的前缀键，还是值得的。

按这个思路整理下来，本文配置下的常用快捷键是：

| 操作 | 快捷键 |
|---|---|
| 新建窗口 | `prefix + c` |
| 切换到下一个窗口 | `prefix + n` |
| 列出窗口并选择 | `prefix + "` |
| 左右切分面板 | `prefix + \` |
| 上下切分面板 | `prefix + -` |
| 在面板间移动 | `prefix + h/j/k/l` |
| 调整面板大小 | `prefix + < / > / _ / +` |
| 关闭当前窗口（会先确认） | `prefix + K` |
| 离开会话（detach） | `prefix + d` |
| 给程序发送 Ctrl+a | `prefix + a` |

---

## 四、使用细节的调优

### Esc 键延迟

**现象**：在 tmux 里用 vim，按 Esc 退出插入模式，要顿一下才生效。

**原因**：终端里 Alt 组合键是以 Esc 开头的一串字符发出来的。tmux 收到 Esc 后，会等一小段时间看后面还有没有字符，以区分“单独的 Esc”和“Alt+某键”。较老的版本默认要等 500 毫秒。

**解决**：`set -sg escape-time 0`，不再等待。

### 历史输出的回看

**现象**：程序打了一大屏日志，想往回翻，很快就到头了。

**原因**：tmux 每个 Pane 默认只保留 2000 行历史。另外，tmux 默认会让外层终端进入“备用屏幕”，所以外层终端（比如 iTerm2）自带的滚动条也翻不到 tmux 里的历史。

**解决**：`set -g history-limit 10000` 把历史调大；`terminal-overrides 'xterm*:smcup@:rmcup@'` 关掉备用屏幕，让外层终端的滚动条也能用。想在 tmux 里翻历史、搜索、复制，则用 `prefix + [` 进入复制模式，配合 `setw -g mode-keys vi`，就能用 vim 的按键来移动和搜索。

---

## 完整配置文件（tmux 2.9+）

把前文各节的改动合在一起，就是下面这份配置。样式相关的配置全部用 `-style` 选项来写：tmux 2.9 删掉了旧的 `-fg`、`-bg`、`-attr` 选项（它们早在 1.9 就被 `-style` 取代）[^changes]，网上不少老配置在新版本上会报 invalid option，这份配置则可以直接加载。

```bash
# ~/.tmux.conf

# ── 前缀键 ──────────────────────────────────────────────
unbind C-b
unbind &
unbind %
unbind w
unbind '"'

set -g prefix C-a          # 将前缀键改为 Ctrl+a，更顺手
bind a send-prefix         # 解决与 bash Ctrl+a（行首跳转）的冲突

# ── 窗口与面板切分 ───────────────────────────────────────
bind '\' split-window -h                                          # 左右切分
bind -   split-window -v                                          # 上下切分
bind K   confirm-before -p "kill-window #W? (y/n)" kill-window   # 关闭窗口前确认
bind '"' choose-window                                            # 窗口选择列表

# ── 面板导航（vim 风格）──────────────────────────────────
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# ── 面板大小调整 ─────────────────────────────────────────
bind < resize-pane -L 5
bind > resize-pane -R 5
bind _ resize-pane -D 5
bind + resize-pane -U 5

# ── 窗口样式（-style 写法）─────────────────────────────
setw -g window-status-current-style  'bg=red,fg=white,bold'
setw -g window-status-style          'bright'
setw -g window-status-format         '#[fg=cyan,dim]#I#[fg=blue] #[default]#W#[fg=grey,dim]'
setw -g window-status-current-format '#[fg=cyan,dim]#I#[fg=blue] #[default]#W#[fg=grey,dim]'

# ── 状态栏（-style 写法）───────────────────────────────
set -g status-style       'bg=blue,fg=white'
set -g status-left        ""
set -g status-left-length 0
set -g status-interval    2
set -g status-right       "%Y/%m/%d %H:%M"
set -g default-terminal   "screen"

# ── 历史与滚动 ───────────────────────────────────────────
set -g history-limit      10000
set -g terminal-overrides 'xterm*:smcup@:rmcup@'
setw -g mode-keys vi       # 复制模式使用 vi 按键

# ── 其他 ─────────────────────────────────────────────────
set -sg escape-time 0      # 消除 Esc 键延迟
```

配置写好后，在 tmux 里执行以下命令立即生效：

```bash
tmux source ~/.tmux.conf
```

---

## 小结

回头看这一路，每一步都是被一个具体的问题推着走的：

1. **程序的长时间运行**：程序的生命周期被绑在了 SSH 连接上。tmux 用客户端 / 服务端架构把程序挂到常驻的服务端上，断开的只是客户端。
2. **多任务的组织**：以 SSH 连接为单位组织工作，粒度太粗。Session、Window、Pane 三层结构让一次连接就能摆开整套工作环境。
3. **快捷键的定制**：改用 `Ctrl+a` 作前缀，又与 bash 的行首跳转冲突。理解了按键表，就知道用 `send-prefix` 留一个出口。
4. **使用细节的调优**：Esc 延迟、历史行数不足，分别交给 `escape-time` 和 `history-limit` 等选项。

把上面的配置保存到 `~/.tmux.conf`，下次再遇到断线，只需要一句 `tmux attach`。

---

## 参考资料
