平时用 tmux 的人,多多少少都会遇到这种场景:窗格里跑完一个大任务,满屏的输出,想原样保存下来发给同事参考,或者在自己的本地剪贴板里留一份。用鼠标直接选中吧,轻则断行,重则滚轮一划,选中的内容全没了;想从日志文件里重新截取,又要先去翻路径、定位行号,费时费劲。这套“tmux 窗格内容全选输出到文件/本地剪贴板”的操作我折腾过好几回,从 copy mode 到 pipe 命令,最后定下来的方案其实很简单:让 tmux 自己把当前窗格的缓存内容整体交出来,用管道喂给文件重定向或系统剪贴板工具。这篇文章就是从我的自用配置里整理出来的,把原理、快捷键、脚本和踩过的坑一次讲完。
这些命令在 tmux 3.x 下都实测过。无论你是把 tmux 当终端管理器重度使用,还是刚接触它的新手,只要遇到“想把屏幕上这一大块内容干干净净复制出来”的需求,下面的内容都能直接用。
1. 先理清楚需求,再选方案
1.1 我们到底想从窗格里拿到什么
很多人第一次想到的解决方案是用鼠标从终端里复制。在普通终端里这么做没问题,但在 tmux 里会碰到几个坎:
第一,tmux 窗口里的内容不但是在内存中渲染的,还分管历史和当前可见区。屏幕只是“窗口”,真正的内容在 tmux 的 buffer 和 history 里。用鼠标直接拖选,拖的是终端渲染结果,长行会被折行截断,复制出来贴到别处,每行都带着终端宽度的“折行痕迹”。
第二,鼠标选择对“全部内容”这个需求不友好。窗格里跑完构建、测试、大段日志输出,可能向上滚动了好几千行,光靠鼠标拖选很容易漏掉中间部分,也特别费眼睛。
第三,我希望管线化。复制出来的文本最好能直接进入管道,既能写到文件里,又能送给剪贴板工具,还能在保存前做一次清洗(比如去掉 ANSI 颜色码)。鼠标复制做不到这一点。
所以,从这里开始,我一直坚持“先用 tmux 自己的命令把内容接管过来,再决定去向”的思路。核心需求其实就是三句话:能拿到全量历史内容、能保留原始换行和格式、能通过管道或重定向落盘/进剪贴板。
1.2 几条常走的路线对比
想要把窗格内容弄出来,网上能看到的方法大概有四类。我按“适合我的程度”排个序,放在下面这张表里。
| 方案 | 思路 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| copy mode + 鼠标选择 | 进入复制模式后用鼠标/键盘选中,yank 进 tmux buffer | 操作直观,适合小段文字 | 大段内容容易选错,折行处理靠手工 | 随手复制一两行 |
| copy mode + buffer 中转 | 复制进 tmux buffer 后,再用 save-buffer 导出到文件 | 灵活,tmux buffer 不怕会话中断 | 操作链路长,无法一键拿到整窗格 | 需要先筛选内容再导出 |
| capture-pane 管道 | 将窗格内容直接输出到 stdout,配合重定向或剪贴板工具 | 稳定可靠,适合全量抓取,可完全自动化 | 需要记参数,绑快捷键需要配置 | 日常归档、分享、日志采集 |
| set-clipboard + OSC52 | 通过终端转义序列把 tmux 复制内容同步进系统剪贴板 | 复制后全自动,无需额外工具 | 依赖终端支持,不是所有终端都可用 | 日常快速复制 |
我现在的使用习惯是:随手复制小段文字就走 copy mode 或 OSC52;整窗格保存或发给别人一律走 capture-pane 管道。为什么最后会选 capture-pane?因为它根本不经过“选中”这个步骤,tmux 的窗格缓存里有多少行,它就输出多少行,只要历史缓冲区够大,就能拿到完整内容。而且它天然就是文本流,接文件、接剪贴板、接其他命令都特别顺手。
2. copy mode 和 buffer:理解基础,才能走得更远
2.1 copy mode 的基本操作
在深入 capture-pane 之前,还是先把 tmux 最基本的“复制”路径走一遍。毕竟 capture-pane 能抓全量,但如果你想从一大段历史里挑出中间一小块,copy mode 仍然是最快的。
默认情况下,按前缀键(通常是Ctrl-b)再按[进入 copy mode。进入之后可以用方向键或PageUp、PageDown翻看历史。选中内容的方式取决于你启没启用 vi 模式。我在.tmux.conf里写的是:
set -g mode-keys vi这样进入 copy mode 之后,就能用j、k上下移动,h、l左右移动,按空格开始选中,按回车把选中的内容复制进 tmux buffer,按]在窗格中粘贴 buffer 里的内容。
这里有个容易忽略的地方:复制完成后,内容并不是直接进你的系统剪贴板。它先进的是tmux 自己的 paste buffer,这是 tmux 内部管理的一块内存区域。默认情况下 buffer 和系统剪贴板是两条线,并不会自动互通。很多新手以为按了回车就能Ctrl-v贴到浏览器里,结果发现完全没反应,就是因为没理解这个机制。
2.2 buffer 到底能干什么
tmux 的 paste buffer 其实很强大。由于它在 tmux 内部,哪怕你从一个窗格复制完立刻切到另一个窗格,它也不会丢。而且 buffer 可以同时保存多条历史记录,用以下命令查看和管理:
tmux list-buffers # 列出所有 buffer tmux show-buffer # 显示最近的 buffer 内容 tmux show-buffer -b buffer-name # 显示指定 buffer tmux delete-buffer -b buffer-name # 删除指定 buffer tmux save-buffer -b buffer-name -a /path/to/file # 把指定 buffer 追加写入文件save-buffer的-a表示追加而不是覆盖,这一点特别适合做“把多次复制的零散内容都汇总到一个笔记文件里”这种操作。我早期就是这么干的:随手复制,然后再找一个快捷键把 buffer 统一追加到当天的记录文件里。
值得留意的是,buffer 只是一块“中转内存”。复制完不导出,关掉 tmux server 也就没了。真正要落盘,还是得靠下面这条命令:
tmux save-buffer -a ~/tmux-pastes.log2.3 给 buffer 导出一个快捷键
这里可以顺手绑一个键,把当前 buffer 保存到文件。比如在.tmux.conf里加入:
bind b run-shell "tmux save-buffer -a ~/tmux-pastes.log"这样以后复制完任何内容,按prefix + b就会把最近的 buffer 追加进~/tmux-pastes.log。为什么这里用追加而不是覆盖?因为我大多数情况下是在持续收集资料,覆盖会丢掉之前的内容。如果某一天我只想要当前这一份,也用命令行单独执行一次tmux save-buffer ~/xxx.txt就好。buffer 的路径只是“中转站”,真正的输出方式完全看你在导出时怎么重定向。
不过这套 buffer 流程有一个天然限制:它保存的是你“亲手选中”的内容,而不是整个窗格的完整内容。如果我想把当前窗格从历史第一行到最后一行的东西原样抠出来,copy mode 就不够痛快了,这时候必须请出capture-pane。
3. capture-pane:把整个窗格焊成文本流
capture-pane是 tmux 里一个老牌命令,意思是“捕获当前窗格的内容”。它可以把某个窗格的可见区、以及有历史记录的部分,全部以文本形式输出,甚至可以保留或去除 ANSI 颜色序列。更关键的是,它支持-p参数,直接输出到 stdout,而不是写入 tmux buffer。这就意味着它可以直接接管道。
3.1 capture-pane 的参数逐个说
刚开始用 capture-pane 时,我是记不住参数的。后来发现把它拆成几类就好记了:一是“控制目标”,二是“控制范围”,三是“控制输出形式”。
目标用-t指定。平时操作当前窗格可以直接写成.,也可以写会话名:窗口号.窗格号。比如tmux capture-pane -t 0:1.2 -p表示捕获会话 0 的窗口 1 的窗格 2。先跑一句tmux list-panes -a,能列出当前所有窗格以及它们对应的 ID,方便确认目标。
范围用-S和-E指定。-S是开始行,-E是结束行。这里有个很重要的行为:负数表示“当前可见区域上方的历史行数”,正数表示从当前可见区域顶部往下数。默认不写-S和-E的话,只捕获当前可见的那一屏。想拿全部历史,一般就写很大的负数,比如:
tmux capture-pane -t . -S -5000 -p这个命令的意思是从当前窗格可见区域往上数 5000 行开始捕获,一直到当前可见区域底部。如果历史不足 5000 行,它会从最早能拿到的行开始,不会因此报错。注意,能拿多少,很大程度上取决于 tmux 的history-limit配置,这个后面会细说。
输出形式用-p、-J、-e控制。-p是输出到标准输出;-J是合并折行,把终端里因为宽度不够而自动换行的逻辑行还原成完整的一行;-e则是保留转义序列,也就是 ANSI 颜色码。我的建议很简单:需要保存为纯文本、发给别人看的时候,不要加-e;需要保留原始高亮或精确复现格式时再加。默认不带-e,输出的就是干净文本,正好满足大多数归档需求。
3.2 把内容写进文件:一行命令
说干就干,把当前窗格内容存到文件里,我最常用的命令是:
TARGET=~/tmux-dumps/$(date +%Y%m%d-%H%M%S).log mkdir -p ~/tmux-dumps tmux capture-pane -t . -S -3000 -J -p > "$TARGET"先建目录,再用带时间戳的文件名避开覆盖,接着重定向。这里-J很关键,尤其是在跑日志、构建输出的时候,没有它,被折行的长行会被硬生生切成好几段,贴出来还得手工拼回去。-S -3000是一个平衡值:历史够大,又不至于让输出太大。如果你的 tmux 把history-limit调到很大,这里也可以跟着调大。
如果你想一次抓多个窗格,可以用 for 循环:
for pane in $(tmux list-panes -a -F '#{pane_id}'); do tmux capture-pane -t "$pane" -S -1000 -J -p > "$pane.txt" done这段会为每个窗格生成一个独立文件,文件名就是窗格 ID。日常排查多窗格问题时会特别好用。
3.3 直接把内容送给本地剪贴板
文件输出虽然稳,但有时候我就想立刻把内容粘贴到聊天工具里,这时候就需要管道到“本地剪贴板”了。关键点在于不同系统下的剪贴板工具不一样:
- Linux 的 X11 环境:
xclip或xsel - Linux 的 Wayland 环境:
wl-copy - macOS:
pbcopy - Windows / WSL:通常是
clip.exe或win32yank.exe
最经典的三条命令:
# Linux,以 xclip 为例 tmux capture-pane -t . -S -3000 -J -p | xclip -selection clipboard # macOS tmux capture-pane -t . -S -3000 -J -p | pbcopy # Windows 的 WSL 环境 tmux capture-pane -t . -S -3000 -J -p | clip.exexclip -selection clipboard表示写入系统剪贴板的主选区和剪贴板区域。pbcopy是 macOS 自带的。clip.exe在 WSL 里可以直接把标准输入转发给 Windows 剪贴板,平时用起来很方便。
这几个工具本质上都是“把 stdin 的内容放进剪贴板”的薄封装,所以关键不在工具本身,而在你能否把 tmux 的输出正确喂过去。一旦这条管道跑通,后面做快捷键就非常顺。
3.4 绑两个快捷键,从此不再敲命令
每次需要的时候手动敲命令还是太啰嗦了。我在.tmux.conf里加了两个绑定:一个负责保存文件,一个负责复制到剪贴板。
bind C run-shell "tmux capture-pane -t . -S -3000 -J -p > ~/tmux-dumps/pane-$(date +%s).log" bind P run-shell "tmux capture-pane -t . -S -3000 -J -p | xclip -selection clipboard"大写C和P都是空闲键位,不会和常见操作冲突。按prefix + C,当前窗格内容就会带时间戳存入~/tmux-dumps/;按prefix + P,内容直接进系统剪贴板。如果你在 macOS 上,把P那行的xclip换成pbcopy;如果在 WSL 里,就换成clip.exe。
有个细节需要提醒:run-shell里的命令是在 tmux 的子 shell 里执行的,它会继承 tmux 的环境变量,但不一定有你交互 shell 里的完整PATH。如果某些工具明明装了却提示找不到,可以在.tmux.conf里先用setenv -g PATH把路径补全,或者在命令中使用绝对路径。
3.5 如何指定“不是当前窗格”的目标
上面绑定的快捷键默认都是抓当前窗格。但 tmux 有一个特性:你在任意窗格里按快捷键,-t .永远指向“按快捷键那一刻所在窗格”。这通常没问题,可如果你想要从一个后台停滞的窗格里抓内容,一边看另一个窗格,一边操作,就得按目标来指定。
先列出所有窗格:
tmux list-panes -a输出里会显示session:window.pane这样的目标描述,也会显示pane_id。比如看到一个0:1.2,意思是会话 0、窗口 1、窗格 2。抓这个窗格的内容可以写:
tmux capture-pane -t 0:1.2 -S -2000 -p > /tmp/from-pane12.log在脚本和绑定里我更推荐用#{pane_id}这种变量,比如:
tmux capture-pane -t '#{pane_id}' -S -2000 -p | xclip -selection clipboard因为pane_id在整个 tmux server 生命周期内是稳定的,不像窗格编号那样会随拆分合并而变动。这个习惯养成后,脚本稳定性会明显提升。
4. 进阶玩法:OSC52、封装脚本和常见坑
4.1 OSC52:复制之后自动同步到本地剪贴板
前面提到的 capture-pane 管道方案,本质是“手动指定流向”。还有一个更自动的方案,很多人都忽略了:tmux 的set-clipboard配置。它的作用是在你从 copy mode 按下回车复制时,tmux 通过终端发送一段 OSC52 转义序列,由终端把内容写入系统剪贴板。
也就是说,如果你在.tmux.conf里写了:
set -g set-clipboard on然后正常用 copy mode 选中一小段内容,复制动作结束的一瞬间,内容就已经在系统剪贴板里了,可以直接Ctrl-v粘贴。不需要 xclip,也不需要 pbcopy。
这个方案听起来最优雅,但有明显前提:你的终端必须支持 OSC52。比较常见的比如 iTerm2、kitty、较新版本的 Windows Terminal,都对 OSC52 支持得不错。如果你的终端不支持,这段配置不会生效,也不会报错,只是系统剪贴板里始终没有内容罢了。
我的态度是:把set-clipboard on当作“增强项”,该配置配置上。日常快速复制小段内容,用到它的概率很高;真正要抓整窗格的时候,还是优先绑定的prefix + C/prefix + P,因为 OSC52 只处理你选择的内容,不会自动帮你抓一整个窗格几千行。
4.2 封装成脚本:参数化才是长期好用的关键
快捷键绑多了以后,我很快发现一个问题:写在.tmux.conf里的命令一旦复杂起来,引号转义就非常折磨人。后来我把逻辑抽到了独立脚本里,配置文件只留一行调用代码。这样的好处是脚本可以用 bash 完整写条件判断,也方便多加几个参数。
我现在的~/.tmux/scripts/dump-pane.sh大致长这样:
#!/usr/bin/env bash # 保存当前窗格内容到文件 # 用法: dump-pane.sh [目标文件] set -euo pipefail TARGET="${1:-$HOME/tmux-dumps/pane-$(date +%Y%m%d-%H%M%S).log}" mkdir -p "$(dirname "$TARGET")" tmux capture-pane -t '.' -S -3000 -J -p > "$TARGET" echo "saved to: $TARGET"剪贴板版本是这个样子:
#!/usr/bin/env bash # 复制当前窗格内容到本地剪贴板 set -euo pipefail if command -v xclip >/dev/null 2>&1; then tmux capture-pane -S -3000 -J -p | xclip -selection clipboard elif command -v pbcopy >/dev/null 2>&1; then tmux capture-pane -S -3000 -J -p | pbcopy elif command -v clip.exe >/dev/null 2>&1; then tmux capture-pane -S -3000 -J -p | clip.exe else echo "no clipboard tool found, output to stdout instead" >&2 tmux capture-pane -S -3000 -J -p fi然后在.tmux.conf里绑定:
bind D run-shell "~/.tmux/scripts/dump-pane.sh" bind L run-shell "~/.tmux/scripts/copy-pane.sh"这里有个值得专门说明的细节:脚本里我用了一串command -v来判断系统里到底有哪些剪贴板工具。不同发行版、不同工作环境,装的东西千差万别。直接写死xclip,切到 Wayland 环境就会哑火;写死pbcopy,回到 Linux 又找不到命令。所以脚本应该“探测环境、选择工具、执行动作”,而不是“默认我有某个工具”。这套模式不光适用于 tmux,放在任何跨平台 shell 工具里都成立。
4.3 常见问题与排查速查表
这部分我总结了实际操作里遇到的几个高频问题,直接整理成了一张表。
| 现象 | 原因 | 解决办法 |
|---|---|---|
tmux capture-pane -S -3000 -p拿到的内容还是只有一屏 | tmux 的history-limit设置太小,历史行数不足 3000 | 在.tmux.conf里设置set -g history-limit 10000或更大,重开会话后生效 |
保存的文件里全是^[[31m这类颜色控制字符 | 可能误加了-e参数,或者源程序输出本身就带 ANSI 控制序列 | 去掉-e;如果已经生成,用sed 's/\x1b\[[0-9;]*m//g'清洗 |
| 复制到剪贴板后内容被截断 | 剪贴板工具对超长文本有处理上限,常见于 xclip | 改走文件输出,不要硬塞剪贴板;几 MB 以上的内容建议直接落盘 |
| 文件里的长行被切成一段段,贴出来排版全乱了 | 没有加-J,终端宽度折行被原样保留 | 统一在 capture-pane 后面加-J |
| 从 copy mode 复制后系统剪贴板里没内容 | set-clipboard未开启,或终端不支持 OSC52 | 改用管道方案,直接| xclip -selection clipboard |
run-shell绑定后提示date: command not found | tmux 子 shell 的PATH不完整 | 在.tmux.conf里补setenv -g PATH $PATH,或改用绝对路径 |
| 抓到的内容不是目标窗格 | -t写错了目标,或者使用了不稳定的窗格编号 | 用#{pane_id}这类稳定标识,或先运行tmux list-panes -a确认 |
| 历史内容抓不全 | 窗格内的 history 在创建窗格时被限制,或者会话重启导致历史丢失 | 调大history-limit,并把重要输出及时落盘 |
每一次踩坑,本质上都是在提醒我:tmux 的历史缓冲区是窗格内容的唯一来源,历史被截断,capture-pane 再好也无力回天。所以我把history-limit调到了 20000。内存会稍微多一些,但一个窗格几千行文本在内存里真的不算什么,比起抓不到内容带来的损失,这点成本很划算。
还有一个小坑:capture-pane默认抓到的范围是当前可见区,容易让人误以为命令无效。其实只要默认不带-S,就只输出一屏。这不算 bug,是行为预期问题。所以看到“只有一屏”的时候,先想想是不是历史行数不足,以及是不是没写-S。
4.4 几个提升体验的操作心得
这套方案用了快一年,有几个心得算是“不写下来会忘”级别的。
第一,文件名一定要带时间戳。保存内容这件事,绝大多数场景都是“防丢”而不是“归档到固定文件”。如果每次写同一个路径,第二次就把第一次覆盖了。我现在的文件命名格式是pane-YYYYmmdd-HHMMSS.log,保证了基本不会撞名。脚本里用date +%s(Unix 时间戳)也可以,二者选一个就行。
第二,剪贴板方案和文件方案不要混着用。有时图省事,我尝试过把剪贴板的内容再手动粘进文件,结果发现粘贴过程中如果触发终端多行处理,格式会莫名其妙多出空行。现在已经固定下来:想长期保存就走prefix + C落盘,想临时粘贴就走prefix + P进剪贴板。两个通道各管各的,互不污染。
第三,配置文件本身纳入版本管理。.tmux.conf里的绑定、脚本里的判断,都算是我个人的“生产工具”了。我会把整个~/.tmux/目录用 git 管理,换电脑或重装系统后直接拉下来,软链到 home 目录就能恢复完整环境。绑定的快捷键也一样,只要配置在,习惯就不会丢。
第四,“全选输出”不等于“无限输出”。再大的history-limit也有上限,而且 tmux 重启、关闭窗格,历史记录照样会丢失。真正重要的输出,应该让程序自己写日志文件,或者在任务结束时顺手按两下prefix + C落盘。把 tmux 当成“短期流动缓存”,把文件系统当成“长期归档”,这个边界想清楚,就不会在关键时刻丢东西。
5. 写在最后的经验
这篇文章里的命令和配置,几乎都是我在真实工作流里一遍遍打磨出来的。我现在最常用的动作就两个:prefix + C保存当前窗格到~/tmux-dumps/,prefix + P复制到本地剪贴板。偶尔需要精确截取某一段历史时,再进 copy mode 配合 vi 快捷键手工选中。自从用了这套流程,我再也没有因为“终端复制粘贴出错”这件事烦心过。
如果你刚开始使用 tmux,建议先复制第三节里最简单的两条命令去试;跑通之后再改造成自己的快捷键和脚本。这里所有配置都以 tmux 3.x 为准,不同小版本之间命令差异极小,放心使用。把这套东西放进自己的~/.tmux.conf里,慢慢就会感觉到,tmux 不只是窗口管理器,它还是你处理文本的“第二双手”。