Chips

tmux 实用要点:会话、窗格与脚本化用法

· 约 4 分钟 · 1356 字

会话独立于终端连接而存在,这是远程运维里最核心的价值;以及 send-keys / capture-pane 让交互式程序可被脚本驱动。最后说明何时该改用 systemd 而不是「挂在 tmux 里」。

为什么需要它

SSH 会话断开会杀掉该会话的前台进程(收到 SIGHUP)。对一条要跑二十分钟的升级、备份或构建来说,这意味着一次网络抖动就前功尽弃,更糟的情况是留下半完成状态。

tmux 的会话独立于终端连接而存在:连接断了,会话里的进程继续跑,随时可以重新 attach 回去。这是它在运维场景下唯一真正不可替代的价值,其余都是便利性。

会话

tmux new -s deploy        # 新建并命名后进入
tmux ls                   # 列出全部会话
tmux attach -t deploy     # 重新接入
tmux kill-session -t deploy

在会话内用 Ctrl-b d(detach)离开但不关闭——这是最常用也最容易误解的一对概念:

操作 进程 之后能回来吗
Ctrl-b d detach 继续运行 能,attach 回去
直接关终端窗口 继续运行
exit / 会话内最后一个程序退出 结束 不能
kill-session 结束 不能

给会话起名是有意义的。 不命名的话回来时 tmux ls 只看到一串编号(0:1:),在多个会话并存时完全无法分辨哪个是干什么的。用 deploydb-migrate 这种名字,几周后再看也知道是什么。

窗口与窗格

两者是不同的层级,容易混:

常用键(默认前缀 Ctrl-b):

作用
c 新建窗口
n / p 下一个 / 上一个窗口
0-9 跳到第 N 个窗口
% 纵向分屏(左右)
" 横向分屏(上下)
方向键 在窗格间切换
z 当前窗格全屏 / 还原(toggle)
x 关闭当前窗格
, 重命名当前窗口

z 值得单独记:在一个窄窗格里看长输出很痛苦,Ctrl-b z 把它临时放大到整屏,再按一次还原,布局不受影响。

滚屏与复制模式

在 tmux 里鼠标滚轮或 PageUp 的行为与普通终端不同,因为终端的历史缓冲被 tmux 接管了。要翻看输出需要进入复制模式

Ctrl-b [          进入复制模式
  ↑ / ↓ / PgUp     滚动
  Ctrl-b           (在 vi 模式下)开始选择
  Enter            复制选中内容
  q                退出复制模式

脚本化用法(最值得记住的部分)

tmux 可以被脚本驱动,这让它成为控制交互式程序的实用工具——那些没有批处理模式的程序(某些安装器、REPL、调试器)只能通过终端交互,而 send-keys + capture-pane 恰好能替代人工输入输出。

# 后台新建会话并运行命令(-d = detached,不接管当前终端)
tmux new -d -s build 'make -j4 2>&1 | tee /tmp/build.log'

# 向会话注入输入(模拟敲键盘,Enter 表示回车)
tmux send-keys -t build 'make clean' Enter

# 读取当前屏内容(用于脚本检查交互式程序的输出)
tmux capture-pane -p -t build

# 带历史行数读取
tmux capture-pane -p -S -200 -t build

一个完整的"后台跑完再取回结果"的例子:

tmux new -d -s upgrade
tmux send-keys -t upgrade 'apt-get -y upgrade; echo "EXIT=$?"' Enter

# 之后轮询检查是否结束
tmux capture-pane -p -t upgrade | tail -5

# 确认结束后取回完整输出并清理
tmux capture-pane -p -S - -t upgrade > /tmp/upgrade.out
tmux kill-session -t upgrade

-S - 表示从历史缓冲的最开头取,-S -200 表示往上取 200 行。默认只取当前可见屏——这在"输出很长、重要的部分已经滚过去了"时会造成误判,因此取完整输出时要用 -S -

注意 send-keys 的转义:内容里含引号或 $ 时要当心外层 shell 与 tmux 的两次解析。稳妥做法是写成脚本文件再 send-keys -t <会话> 'bash /tmp/doit.sh' Enter,避免嵌套引号。

与 systemd 的取舍

这两者的分界很清楚,混淆会带来麻烦:

需求 用什么
一次性长任务(升级、备份、构建、数据迁移) tmux(或 nohup
需要开机自启、崩溃重试、日志归集的服务 systemd

服务挂在 tmux 里是常见的错误做法,它缺三样东西:

  1. 重启后不会恢复——tmux 会话本身不是开机自启的(即使配置了,也没有崩溃重试)
  2. 没有失败重试——进程挂了就一直挂着,没人拉起
  3. 日志不归集——输出散在某个窗格里,journalctl 查不到,出事后无法回溯

一个长期运行的服务应该是:

[Unit]
Description=My service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/myservice
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Type=simple + Restart=on-failure 就提供了 tmux 给不了的自愈能力,而日志自动进 journal。用 tmux 跑服务,本质上是在手工重造一个更差的 systemd。

反过来,给一次性任务写 systemd 单元也是过度设计——你并不需要它开机自启,只需要它这次跑完。这时候 tmux 是更轻的选择。

判断要点

← 全部文章