第 6 章 工作流
就 tmux 自身来说,它只是一个添加了一些附加功能的另一个终端而已,只是显示了更多的终端会话。但是 tmux 让我们在这些会话里运行程序时更加方便,所以本章将会探讨一些常见、不常见的配置和命令,它们可能对你的日常工作有很大的帮助。你会学到管理面板和会话的高级方式,如何让 tmux 和 shell 一起工作,如何使用外部脚本扩展 tmux 命令,如何创建能执行数条命令的快捷键。我们先从管理窗口和面板开始。
6.1 高效使用面板和窗口工作
在本书中,你已经见到数种方式把 tmux 会话分割为多个面板和窗口。在本节,我们会学习使用面板和窗口工作的几种高级方式。
把面板变为窗口
面板很适合用来划分工作空间,但是有时我们需要把一个面板“弹出(pop out)”变为一个独立的窗口,这样看这部分内容就会更方便。tmux 有这样一个命令来实现这个功能。
在任意面板内,按下 PREFIX !
键,tmux 就会依据当前面板创建一个新的窗口。
把窗口变为面板
有时候,我们需要合并一个工作空间,我们可以很简便地把窗口变为一个面板。为此,我们要提一下 join-pane
命令。
在“合并(join)”一个面板时,我们有可能把面板从一个会话移动到另一个会话里。此时需要指定源窗口和面板,后面跟随目标窗口和面板。如果不指定目标窗口,那么当前焦点窗口就会作为目标窗口。
下面我们通过创建一个带有两个窗口的 tmux 会话来演示:
$ tmux new-session -s panes -n first -d
$ tmux new-window -t panes -n second
$ tmux attach -t panes
现在,要把第一个窗口移动,作为第二个窗口的一个面板,按下 PREFIX :
键进入命令模式,然后输入这些:
join-pane -s 1
这句话的意思是“取出窗口(当窗口中有多个面板时则指取出面板,译者注)1 并把它添加到当前窗口”,因为我们没有指定一个目标窗口。
也可以使用这种方法来移动面板。如果第一个窗口有两个面板,可以像下面这样指定源面板,注意我们设置窗口从 1 开始编号,而面板从 0 开始编号:
join-pane -s 1.0
在这里,我们取出了第一个窗口的第一个面板然后把它添加到当前窗口。
更进一步地,甚至可以指定一个源会话,使用格式 -t [session_name]:[window].[pane]
指定一个目标窗口。
最大化和恢复面板
有时我们只是想让一个面板最大化显示一会,这样就可以细看它的内容,这时可以使用 break-pane
命令,然后再使用 join-pane
命令把它放回原处。这个操作做起来有些繁琐,因此我们编写一个脚本来实现这个功能。这是链接。
首先,我们释放 UP
箭头键,把它设置为最大化命令。然后,创建一个新的快捷键 PREFIX UP
来触发这个 tmux 命令串,配置如下:
unbind Up
bind Up new-window -d -n tmp \; swap-pane -s tmp.1 \; select-window -t tmp
在配置里,我们创建了一个名为 tmp 的新窗口。通过给它命名,可以在子序列命令里调用它。当使用 -d
参数创建窗口时,tmux 会在后台创建这个窗口而不是把焦点转到这个窗口上。然后使用 swap-pane
命令选取已经选择的面板和临时窗口的已有面板进行交换。
要恢复窗口,只需要使用 swap-pane
命令把面板从临时窗口交换到原来的窗口里,选择源面板,然后杀掉临时窗口。我们把这个命令序列绑定到 PREFXI DOWN
键,就像这样:
unbind Down
bind Down last-window \; swap-pane -s tmp.1 \; kill-window -t tmp
由于它使用了 last-window
命令来返回源窗口,因此这个过程看起来就像是把一个面板啪的一下最大化了,然后又啪的一下把它恢复原位置,这个简单的例子说明了 tmux 高度灵活性。我们可以通过一个简单的快捷键来自动化实现一系列命令。
在面板里执行命令
在第 3 章,我们已经学习了如何使使用 shell 命令和 send-keys
在面板里启动程序,我们还可以让 tmux 在新建一个窗口或面板时自动执行命令。
假设有两台服务器,bums 和 smithers,分别是 web 服务器和数据库服务器。当启动 tmux 时我们想让 tmux 使用一个窗口的两个面板分别连接到这两台服务器上。
下面我们来创建一个新的名为 servers.sh
的脚本然后创建一个会话连接到两台服务器:
$ tmux new-session -s servers -d "ssh deploy@bums"
$ tmux split-window -v "ssh dba@smithers"
$ tmux attach -t servers
新建一个会话时,可以把要执行的命令作为最后一个参数传入到 tmux 中。在这里我们先是新建了一个会话然后在第一个窗口连接到 bums 服务器,然后从会话中分离出来。之后我们使用垂直分割切分窗口然后连接到 smithers 服务器。
这个配置有个副作用:从远程服务器上退出登录时,面板或窗口会关闭。
在 OS X 系统的同一目录下打开新面板
在 Linux 系统上创建 tmux 新的面板时,新面板使用的是当前面板的路径。但是在 OS X 系统上,新的面板会位于启动 tmux 会话时的那个目录。只需要做一点小小的工作,就可以在打开一个面板时捕捉它的工作路径然后自动地切换路径,就像 Linux 做的那样。
为此,我们使用 send-keys
命令来调用一个脚本把当前路径保存到环境变量中,然后这个脚本回调 send-keys
向拆分的窗口中发送命令,再把路径更改为环境变量中保存的那个路径。
首先,在主目录下创建一个名为 ~/tmux-panes
的新文件,写入以下内容:
TMUX_CURRENT_DIR=`pwd`
tmux split-window $1
tmux send-keys "cd $TMUX_CURRENT_DIR;clear" C-m
然后编辑 .tmux.conf
文件来调用这个文件做垂直和水平分割。这里使用 PREFXI v
键和 PREFXI n
键,以防覆盖了当前已有的分割快捷键。代码如下:
unbind v
unbind n
bind v send-keys " ~/tmux-panes -h" C-m
bind n send-keys " ~/tmux-panes -v" C-m
就像在之前讨论过的,我们需要使用 -v
参数来水平分割窗口,使用 -h
参数来垂直分割窗口。
最后,为 tmux-panes
脚本添加执行权限:
$ chmod +x ~/tmux-panes
在重新加载 .tmux.conf
配置文件后就可以分割面板了。
这种方法的弊端是它看起来有点 hack。它把命令输入到已有的 tmux 窗口并执行脚本。也就是说它只能在一个有命令提示符的窗口里才能被触发。所以,如果你的主窗口在运行 Vim,这个命令是不会有效的。即便是把 send-keys
命令换成 run-shell
命令也不会有效,因为新产生的 shell 也没有访问环境变的权限,因此它也就无法处理这个脚本了。但是这个脚本依然是一个比较方便的小技巧,通过自定义键盘快捷键,依然还保留了原始的命令。
6.2 管理会话
随着使用 tmux 越来越顺手,你会发现你会同时使用多个 tmux 会话。例如,你可能会为每个程序都开启一个 tmux 会话,这样就可以保持开发环境的相对独立。tmux 提供了多种特性能让你在管理这些会话时不会感到痛苦。
在会话间移动
单机上的所有 tmux 会话都通过一个服务器进行管理。也就是说我们能够在一台机器上就可以实现在会话之间来回移动。
下面来演示这个过程,我们会启动两个分离的 tmux 会话,一个名为 editor,它打开了 Vim,一个名为 processes 的会话则在运行 top
命令,命令如下所示:
$ tmux new -s editor -d vim
$ tmux new -s processes -d top
可以这样连接到 editor 会话:
$ tmux attach -t editor
然后按下 PREFIX (
键进入前一个会话,按下 PREFIX )
则可以跳转到下一个会话。
还可以使用 PREFIX s
键显示一个会话列表,这样就可以快速地从一个会话跳转到另一个会话。
你可以添加自定义的快捷键到 .tmux.conf
文件里来绑定 switch-client
命令。默认的配置应该是像这样:
bind -r ( switch-client -p
bind -r ) switch-client -n
如果你已经配置了多个工作空间,这样操作会极大地提高你的效率,而且它不需要分离会话再重新连接。
创建或连接到已有会话
到目前为止,我们学会了多种办法在任意时刻创建新的 tmux 会话。然而,事实上还可以判断一个 tmux 会话是否存在,如果存在的话就连接到它。
has-session
命令返回一个可以用在 shell 脚本里的布尔值。可以用它在 bash 脚本做一些类似这样的事情:
if ! tmux has-session -t remote; then
exec tmux new-session -s development -d
# other setup commands before attaching....
fi
exec tmux attach -t development
如果修改这个脚本让它通过参数读取会话名称,你就可以用它来连接或创建任意 tmux 会话。
在会话之间移动窗口
可以把一个会话的窗口移动到另一个会话里。如果已经在一个开发环境里打开了一个进程,现在想把它移动到另一个环境中,或者想合并工作空间时会非常有用。
move-window
命令被映射到快捷键 PREFIX .
键(英文句号键,译者注),这样可以很方便地把要移动的窗口作为当前焦点窗口,按下快捷键,然后输入目标会话即可。
下面演示一下这个过程,创建一个会话,一个名为 editor,一个名为 processes 分别运行了 vim
和 top
命令:
$ tmux new -s editor -d vim
$ tmux new -s processes -d top
我们会把 processes 会话的窗口移动到 editor 会话里。
首先,连接到 processes 会话,就像这样:
$ tmux attach -t processes
然后,按下 PREFIX .
键,然后在显示的命令行里输入 editor。
这会把 processes 会话里的唯一窗口移动出来,因此 processes 会话会自动关闭。如果连接到 editor 会话,就可以看到这两个窗口。
也可以使用 shell 命令来完成这个功能,因此不必在合并窗口时要连接会话。可以这样使用 move-window
命令:
$ tmux move-window -s processes:1 -t editor
这个命令的意思就是,把 processes 会话的第 1 个窗口移动到 editor 会话中。
6.3 tmux 和你的操作系统
既然 tmux 已经变成了你工作流的一部分,那么你肯定想让它和操作系统集成地越紧密越好。在本机,我们会向你展示多种方式,让你的 tmux 和操作系统一起工作。
使用一个不同的 Shell
在本书中,我们使用的 shell 环境都是 bash,但是如果你更喜欢 zsh,你依然可以使用所有的 tmux 优良特性。
可以在 .tmux.conf
文件里明确地设置默认的 shell 环境,就像这样:
set -g default-command /bin/zsh
set -g default-shell /bin/zsh
由于 tmux 只是一个终端复用器而并没有拥有独立的 shell,因此可以精确地指定使用哪个 shell。
默认启动 tmux
可以配置操作系统让它在打开一个终端时自动启动 tmux。通过使用会话名可以创建一个不存在的会话。
当 tmux 在运行时,它会把 TERM
变量设置为 screen
或者是 default-terminal
配置文件里的配置。可以在 .bashrc
文件(OS X 系统是 .bash_profile
文件)里使用这个变量来确定当前是否处于一个 tmux 会话中。我们在第 2 章配置了 tmux 终端为 screen-256color,因此可以使用这样一个脚本:
if [[ "$TERM" != "screen-256color" ]]
then
tmux attach-session -t "$USER" || tmux new-session -s "$USER"
exit
fi
如果没有在 tmux 会话里,我们会尝试连接到一个名为 $USER 的会话里,也就是当前用户名。可以把这个值替换为任意你想要的值,在这里使用用户名能帮助我们避免冲突。
如果会话不存在,tmux 会抛出一个错误,shell 脚本会把这个错误解释为 false
值。然后脚本会继续执行右侧的命令,也就是创建一个以用户名作为名称的会话。然后退出脚本。
当 tmux 会话启动时,它会再次运行配置文件,但是这次它会看到我们处于一个 tmux 会话中,因此就会略过这部分的后续代码,然后继续执行配置文件的其他配置,确保所有环境变量都已被配置。
现在,创建一个新的终端会话时,我们就自动地连接到一个 tmux 会话中。但是请小心,因为你每次打开新的终端窗口时都会连接到相同的会话,在任意终端窗口里输入 exit
命令都会关闭所有连接到会话的终端窗口!
把程序输出记录到日志里
有时需要把一个终端会话的输出记录到日志文件里。我们在之前已经讨论过如何使用 capture-pane
和 save-buffer
命令来完成这些操作,但是实际上 tmux 可以通过 pipe-pane
命令把一个面板里的活动都记录到一个文本文件里。这很像许多 shell 里的 script
命令,使用 pipe-pane
命令可以选择打开或关闭这个功能,而且可以在一个程序已经运行之后再开始使用这个命令。
要激活这个功能,在命令模式输入命令 pipe-pane -o "mylog.txt"
。
-o
参数让我们打开了输出功能,也就是说如果再次发送相同的命令就可以把这个功能关掉。为了更方便地执行这个命令,我们把它添加到配置文件里并绑定一个快捷键。像这样:
bind P pipe-pane -o "cat >>~/#W.log" \; display "Toggled logging to ~/#W.log"
现在可以按下 PREFIX P
命令来控制打开日志功能了。多亏了 display
命令(display-message
命令的简写),我们可以在状态栏看见日志文件名。display
命令和状态栏一样能访问相同的变量。
在状态栏添加电池电量显示
如果你在笔记本电脑上使用 tmux,你可能想在状态栏里显示电池剩余电量,尤其是终端在全屏状态下运行的时候。幸亏有 #(shellcommand)
变量能够让这个事情变得相当容易。
现在我们把电池状态添加到配置文件里。我们抓取了一个简单的 shell 脚本,它能够获取剩余电池电量并把它写入主目录下名为 battary
的文件里。要让 tmux 能够使用这个脚本,要赋予它执行权限。在终端里运行这些命令:
$ wget --no-check-certificate \
https://raw.github.com/richoH/dotfiles/master/bin/battery
$ chmod +x ~/battery
如果在终端里运行这个命令:
$ ~/battery Discharging
我们就会看到电池剩余电量的百分比。可以让 tmux 通过 #(<command>)
命令把它显示在状态栏里。所以,要在时钟之前显示电池电量,需要这样修改配置文件里 status-right
这一行:
set -g status-right "#(~/battery Discharging) | #[fg=cyan]%d %b %R"
重新加载 .tmux.conf
文件时,电池的剩余电量就会显示出来。
要想在电池充电时获取它的状态,需要执行这个命令:
$ ~/battery Charging
然后根据上面的方法,可以把这个命令添加到状态栏里。这部分的工作留给你来完成。
使用这种方法更深度地定制状态栏。只需要编写自己的脚本然后返回你想要显示的任意值,然后把它扔到状态栏里。
6.4 接下来做什么?
你已经学会了 tmux 的基础你就可以做很多事情,而且你现在已经对不同的配置有了一定的经验。tmux 的用户手册,可以在终端里获取,命令如下:
$ man tmux
这里面有完整的配置选项列表和所有 tmux 可用命令。
别忘了 tmux 本身也是在快速进化之中。下一个版本将会带来新的配置选项,会为你带来更高的灵活性。
现在你已经把 tmux 集成到你的工作流之中了,你可以尝试发掘一些其他的常用技术。例如,可以一起使用 tmux 和 Vim 来创建更高效的开发环境。你还可以在 tmux 会话里使用 irssi(一个终端界面的 IRC 客户端)和 Alpine(一个基于终端的邮件应用),每个程序占用你的一个面板,和你的文本编辑器并排排列,或是让它们运行在后台窗口里。然后你可以从会话中分离出来,过段时间再连接到 tmux 会话中,一如既往。