为什么要用 tmux?

Tmux 是一个优秀的终端复用软件,类似 GNU Screen,但来自于OpenBSD,采用BSD授权。利用它,可以保持终端的状态,还可以在一个终端里建立多个 Session、Window、Pane,同时运行多个程序。

由于我国特殊的网络环境,tmux 保持终端状态的功能非常实用。我们与服务器的 ssh 连接随时可能会断开,断开连接后当前 ssh 会话里的任务都会被停止。如果断开的时候我们正在执行比较重要的任务,就会导致任务的失败。

要防止这样的意外中断有很多种方法,最简单的就是使用nohup命令把要运行的命令包裹起来:nohup some important jobs。但是这样子有不少问题,比如不方便运行交互式命令、不方便查看输出、不方便管理;而且最大的问题在于:我们不知道什么时候会断开连接,所以为了防止意外中断,难道要在每一个命令前都加上 nohup?

还有,有些任务可能要执行很久,到了上班或下班的时间还没结束,这时候应该合盖走人还是让它继续运行?如果合盖走人,就会断开 ssh 连接、终止程序;就算让电脑继续运行,也不方便在回家/到公司后接着查看或者操作程序。

最好的办法是使用类似 tmux 这样的工具,在连上 ssh 时开启一个 tmux 会话,在 tmux 里像正常终端一样执行命令。这样就算断开连接也没问题,tmux 依然在运行,里面的程序也受到保护,不会因为断开连接而结束。等到下次 ssh 连接时,连接上之前的 tmux 会话(tmux attach),断开连接前的一切都完整的保留着,可以随意查看输出、继续操作。

为什么不手动启动 tmux

我现在就是这样子的,但是还剩最后一个问题:难道每次都要手动操作吗?难道我每次连接上 ssh,都要先tmux ls看一下有没有之前的会话,然后再决定要使用tmux attach还是tmux new?这也太麻烦了吧!而且也不符合程序员一切都要自动化的好习惯。

在 ssh 连接到服务器时自动启动 tmux

遇到问题当然要先问谷歌,果不其然,在 Stack Overflow 和一个日本问答网站找到了解决方法。

综合参照两者,我得到了自己的解决方案,下面是zsh代码,应该也能用于bash,不过我没试过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if [[ -n "${REMOTEHOST}${SSH_CONNECTION}" && -z "${TMUX}${STY}" ]]; then
# 如果 $REMOTEHOST 和 $SSH_CONNECTION 任意一个不为空,表示这是一个 ssh 连接
# 如果 $TMUX 和 $STY 都为空,表示不是在一个 tmux 或 screen 会话中
NAME=ssh_tmux # tmux 会话的名字
function confirm { # 询问用户是否退出的函数
local MSG=$1 # confirm 函数的第一个参数
while : # 如果用户输入的不是 Y/y/N/n,就一直重复询问
do
echo -n "${MSG} [Y/n]: "
read ans # 获取用户输入
if [ -z "$ans" ]; then # 如果输入为空
ans="y"
fi
case $ans in
[yY]) return 0 ;;
[nN]) return 1 ;;
*) echo Plese input y or n ;;
esac
done
}

if tmux has-session -t $NAME && tmux list-sessions; then
# 如果该会话已存在
echo "tmux attached session"
tmux attach-session -t $NAME
else
echo "tmux created new session"
tmux new-session -s $NAME
fi
# tmux 会话退出或者断开后
confirm "exit?" && exit
fi

把这段脚本放到服务器的 zshrc 里就行了,下次 ssh 连上时会自动开启一个 tmux 会话,在退出或者断开这个会话时会提示是否同时断开 ssh 连接。

这段脚本的一些取舍

为什么要提示是否断开 ssh 连接?

既然已经配置了自动启动 tmux,那么我自然是希望把所有命令都放在 tmux 里跑的,而且好像也不存在什么程序只能在 tmux 外跑,不能在 tmux 里跑。

既然如此,在 tmux 外跑程序已经成为不太常见的需求,每次要断开连接都断开 tmux -> 输入 exit -> 回车有点麻烦,而且可能会忘记断开连接,错把 ssh 服务器当成自己的机子,执行错误的命令。

所以我加上了提示断开 ssh,而且在得到明确的yn前一直提示,防止出错。现在断开连接的流程是断开 tmux -> 回车,少了四次按键。

为什么不自动断开 ssh 连接?

虽然“在 tmux 外跑程序已经成为不太常见的需求”,但偶尔还是可能要这样做的,如果每次都自动断开的话,太不灵活了。

如果我就是不想要这个提示呢?

不自动退出的配置:

1
2
3
if [[ -n "${REMOTEHOST}${SSH_CONNECTION}" && -z "${TMUX}${STY}" ]]; then
tmux attach-session -t ssh_tmux || tmux new-session -s ssh_tmux
fi

自动退出的配置:

1
2
3
if [[ -n "${REMOTEHOST}${SSH_CONNECTION}" && -z "${TMUX}${STY}" ]]; then
(tmux attach-session -t ssh_tmux || tmux new-session -s ssh_tmux) && exit
fi