# 第 3 章 脚本定制 tmux 环境
在项目上工作时,你可能需要运行一大堆的工具和程序集。如果你在做一个 web 应用,你可能需要一个命令窗口,一个文本编辑器,一个数据库命令窗口,和一个运行着你的自动化测试套件的窗口。这样就有大一堆的窗口需要管理,一大堆的命令需要输入。
想象一下你来到了你的工作站前刚坐下,准备开始为你的项目添加新的特性,然后只需要一个简单的命令就可以把这些程序运行起来,每个程序都运行在一个的 tmux 会话中,拥有它自己的面板或是窗口。我们可以使用 tmux 的客户端-服务器模型(client-server model)来创建一个定制的脚本来自动地构建开发环境、分割窗口并运行程序。我们接下来会先学习如何手动执行,然后深入学习高级的自动化工具。
## 3.1 使用 tmux 命令创建一个自定义安装
我们已经学习了如何使用 tmux 命令来创建新的 tmux 会话,但是 tmux 命令还有很多其它的参数可以使用。例如选择一个运行中的会话然后把它的窗口分割成面板,改变布局,甚至是在会话内运行程序。
关键参数是 `-t`,也就是 target。当我们有一个已命名的 tmux 会话,可以像这样连接到它:
```
$ tmux attach -t [session_name]
```
我们使用这个 target 参数来引导命令指向正确的 tmux 会话。所以,如果我们创建了一个新的名为 development 的 tmux 会话,像这样:
```
$ tmux new-session -s development
```
然后我们使用 `PREFIX d` 从这个会话分离出来,那么可以使用这个命令让这个会话水平分割窗口:
```
$ tmux split-window -h -t development
```
执行完命令后,如果再连接到这个会话,我们就会发现窗口已经分割成了两个面板。命令:
```
$ tmux attach -t development
```
实际上,并不需要从 tmux 会话里分离出来再发送命令。可以另开一个终端然后把窗口再次分割,但是这次我们使用垂直分割,命令如下:
```
$ tmux split-window -v -t development
```
通过这种方法可以非常简便地自定义当前已存在的工作环境。下面来看看其它的一些命令。
### 编写一个项目配置脚本
在第一章,我们讨论了譬如 `new-session` 和 `new-window` 的 tmux 命令。下面我们来编写一个简单的脚本,创建一个新的 tmux 会话,它含有多个窗口,有一个窗口会包含多个面板。最重要的是,我们让每个面板都运行不同的程序。
我们从创建一个新的脚本文件开始,在主目录下创建一个名为 `development` 的脚本。然后赋予这个文件可执行权限,这样就可以运行它了。命令如下:
```
$ touch ~/development
$ chmod +x ~/development
```
在这个新的脚本文件里,首先创建一个名为 development 的 tmux 会话,内容如下:
```
tmux new-session -s development -n editor -d
```
在创建这个会话的同时我们给它传入了一些附加的参数。首先,创建这个会话并为它命名,使用了 `-s` 参数。然后把初始窗口命名为 editor,然后再通过 `-d` 参数立即从这个新的会话中分离。
开始使用会话时,我们希望把工作目录变更到项目目录。我们把项目目录称作 devproject。当然,在变更到这个目录之前,最好先创建它,命令如下:
```
$ mkdir ~/devproject
```
目录创建之后,我们把下面一行内容添加到配置文件中,这里使用了 `send-keys` 命令:
```
tmux send-keys -t development 'cd ~/devproject' C-m
```
我们在这行配置的最后添加了一个 `C-m`(Control-M),这样就向 tmux 里发送了一个回车符。接下来我们就可以重复使用这条命令,在窗口里打开 Vim 编辑器。就像这样:
```
tmux send-keys development 'vim' C-m
```
通过这 3 个命令,我们就已经创建了一个新的会话,变更了工作目录,并打开了一个编辑器。但是我们的工作环境还没有配置完成。我们来分割一下主编辑窗口,这样就能在底部拥有一个小的终端窗口。我们使用 `split-window` 命令来完成这项任务。在我们的脚本里,添加这样一行:
```
tmux split-window -v -t development
```
这样就会让主窗口水平地分割。还可以在分割时指定窗口所占的百分比,像这样:
```
tmux split-window -v -p 10 -t development
```
但是我们并不会这样做,我们会把 `split-window` 命令放到一边,然后选择一个 tmux 默认布局—— main-horizontal ——只需要把下面的内容添加到配置中:
```
tmux select-layout -t development main-horizontal
```
我们已经创建了第一个窗口并把它分割为两个面板,但是我们需要让底部的面板需要在打开时就处于项目目录的位置。我们已经知道如何向 tmux 实例发送命令,接下来我们会学习如何向指定的面板和窗口发送命令。
### 指定目标面板和窗口
通过例如 `send-keys` 这样的命令,不仅可以指定目标会话,也可以指定窗口和面板。在第 2 章的配置文件里,我们指定了初始索引为 1,也就是说窗口从 1 开始编号(而不是从 0 开始)。但是这个初始索引并不会影响到面板的索引编号,这也就是我们为什么也要把面板的初始索引设置为 1 的原因。在下面的例子里当前窗口有两个面板,如图9(两个面板)所示:
![document/2015-09-09/55efab36da6cd](https://box.kancloud.cn/document_2015-09-09_55efab36da6cd.jpg)
在 Pane 1 里打开了 Vim 编辑器,然后我们想在 Pane 2 里发送一个命令来改变项目路径。可以通过这种格式来指定一个具体的面板 `[session]:[window].[pane]`,这个例子里就是 `development:1.2`,把下面这行配置添加到配置脚本里,就会得到我们想要的效果:
```
tmux send-keys -t development:1.2 'cd ~/devproject' C-m
```
几乎就要完成了。接下来就可以用我们学到的内容来完成配置,添加一些窗口到会话中。
### 创建并选择窗口
我们想要在会话里有第二个窗口并运行全屏大小的控制台。可以通过 `new-window` 命令来创建新窗口:
```
tmux new-window -n console -t development
tmux send-keys -t development:2 `cd ~/development`C-m
```
创建窗口之后,使用 `send-keys` 命令再次把当前路径改到项目路径。在新窗口里只有一个面板,所以只需指定窗口的编号即可。
启动了新会话后,我们想让第一个窗口显示出来,可以使用 `select-window` 命令:
```
tmux select-window -t development:1
tmux attach -t development
```
可以继续向这个脚本里添加命令,创建更多的窗口和面板、开始到远程服务器的连接、追踪日志文件、连接到数据库控制台、或者在开始工作时运行命令获取最新的代码。但是我们就讲到这里,最后以连接到会话来简单地结束我们的脚本,让我们配置的 tmux 会话显示在屏幕上,准备开始工作。全部脚本如下所示:
```
tmux new-session -s development -n editor -d
tmux send-keys -t development 'cd ~/devproject' C-m
tmux send-keys -t development 'vim' C-m
tmux split-window -v -t development
tmux select-layout -t development main-horizontal
tmux send-keys -t development:1.2 'cd ~/devproject' C-m
tmux new-window -n console -t development
tmux send-keys -t development:2 'cd ~/devproject' C-m
tmux select-window -t development:1
tmux attach -t development
```
运行下面这个命令时,我们就会看到如图10(脚本定制的开发环境)所示的效果:
```
$ ~/development
```
![document/2015-09-09/55efab7d95c61](https://box.kancloud.cn/document_2015-09-09_55efab7d95c61.jpg)
图10 - 脚本定制的开发环境
这种方法的缺点就是这个脚本创建了一个全新的会话。如果你之前已经创建过一个 development 会话,然后再次执行这个脚本的话它就无法正常工作。可以修改脚本,检查是否有同名的会话存在来解决这个问题,使用 `tmux has-session` 命令来检查,然后之后只有在这个会话不存在时才创建这个会话,就像这样:
```
tmux has-session -t development
if [ $? != 0 ]
then
tmux new-session -s development -n editor -d
tmux send-keys -t development 'cd ~/devproject' C-m
tmux send-keys -t development 'vim' C-m
tmux split-window -v -t development
tmux select-layout -t development main-horizontal
tmux send-keys -t development:1.2 'cd ~/devproject' C-m
tmux new-window -n console -t development
tmux send-keys -t development:2 'cd ~/devproject' C-m
tmux select-window -t development:1
fi
tmux attach -t development
```
这样做就能使一个项目配置正常工作了。你可以修改这个脚本,让项目名使用一个变量,这样脚本就能变得通用,但是现在我们来看看另外一些管理多个项目的配置方法。
## 3.2 设置使用 tmux 配置
`.tmux.conf` 文件本身可以包含命令来设置一个默认的环境。如果想让 tmux 会话每次都在相同的默认文件夹路径启动,或者是想让它自动地打开一个分割的窗口,可以把这些写进默认配置文件里,只需使用相似的命令。
还可以指定一个配置文件,让 tmux 实例在启动时加载它,只需使用 `-f` 参数。这种方式就不需要改变默认的配置文件,并且可以在项目中检查配置文件。还可以设置每个项目对应一个配置选项,比如新的键盘快捷键。
让我们试试这个吧,现在创建一个名为 `app.conf` 的配置文件。在这个文件里,可以使用在之前章节使用的相同命令,由于是在配置文件里而不是一个 shell 脚本里,我们就无需特别指定 tmux 的每个命令都使用 `tmux` 前缀,配置文件如下:
```
source-file ~/.tmux.conf
new-session -s development -n editor -d
send-keys -t development 'cd ~/devproject' C-m
send-keys -t development 'vim' C-m
split-window -v -t development
select-layout -t development main-horizontal
send-keys -t development:1.1 'cd ~/devproject' C-m
new-window -n console -t development
send-keys -t development:2 'cd ~/devproject' C-m
select-window -t development:1
```
请注意,在配置文件的第一行就导入了原始 `.tmux.conf` 文件。这样就能使用之前定义的所有环境设置,包括快捷键和状态栏设置。这个导入配置文件的命令并不是强制的,但是如果不导入这个文件,就必须使用所有的默认快捷键和默认选项,或者只能在这个文件里重新定制选项。
要使用这个配置文件,必须要传入 `-f` 参数,后面加上配置文件的路径。还必须使用 `attach` 命令来启动 tmux,就象这样:
```
$ tmux -f app.conf attach
```
这是因为默认 tmux 在启动时总是调用 `new-session` 命令。我们的配置文件已经创建了一个新的会话,所以如果不使用 `attach` 参数的话就会拥有 2 个 tmux 会话。
这种方法有很大的灵活性,可以通过使用一个名为 tmuxinator 的命令行工具做更多的事情。
## 3.3 使用 tmuxinator 管理配置
tmuxinator 是一个简单的工具,可以用它编写并管理不同的 tmux 配置。我们使用简单的 YAML 格式来定义窗口布局和命令,然后就可以使用 `tmuxinator` 命令登录了。和其他方法不同,tmuxinator 为配置文件提供了一个集中的位置和一个更简单的办法来创建复杂的布局。它还允许在每个窗口创建之前运行特定的命令。
tmuxinator 的运行需要 Ruby 解释器,所以你的系统上需要安装了 Ruby 解释器才能使用 tmuxinator。MAC OS X 的用户已经安装了 Ruby,Linux 用户可以通过包管理器安装 Ruby 解释器。如果你打算不仅仅为了使用 tmuxinator 而安装 Ruby 解释器的话,强烈建议通过 RVM 安装 Ruby,你可以依照 RVM 网站的指引来安装 Ruby。[RVM 网站链接](http://rvm.beginrescueend.com/)
我们使用 Rubygems 安装 tmuxinator,Rubygems 是 Ruby 的包管理系统。命令如下:
```
$ gem install tmuxinator
```
如果你没有使用 RVM,你需要 root 权限来执行这条命令或者使用 sudo 命令。
tmuxinator 需要预先定义 shell 环境变量 $EDITOR,所以如果你还没设置的话,可以在 Linux 系统上修改你的 `.bashrc` 文件,或者在 OS X 系统上修改 `.bash_profile` 文件。例如,如果你想把 Vim 作为默认的编辑器,你需要在 Bash 配置文件里增加这条语句:
```
export EDITOR=vim
```
现在就可以创建一个新的 tmuxinator 项目了,我们把它称作 development。命令如下:
```
$ tmuxinator open development
```
这会打开我们预定义的 $EDITOR 变量的编辑器并显示默认的项目配置,就像这样:
```
project_name: Tmuxinator
project_root: ~/code/rails_project
socket_name: foo # Not needed. Remove to use default socket
rvm: 1.9.2@rails_project
pre: sudo /etc/rc.d/mysqld start
windows:
- editor:
layout: main-vertical
panes:
- vim
- #empty, will just run plain bash
- top
- shell: git pull
- database: rails db
- server: rails s
- logs: tail -f logs/development.log
- console: rails c
- capistrano:
- server: ssh me@myhost
```
这是一个 Ruby on Rails 开发者使用 Git 工作的一个非常棒的工作环境。它创建了一个含有 8 个窗口的 tmux 会话。第 1 个窗口被分为 3 个面板,使用了 main-vertical 界面布局。剩下的窗口打开了一些服务器连接和控制台,如图11(使用 tmuxinator 默认配置的 Rails 工作环境)所示:
![document/2015-09-09/55efab9182e28](https://box.kancloud.cn/document_2015-09-09_55efab9182e28.jpg)
图11 - 使用 tmuxinator 默认配置的 Rails 工作环境
如你所见,tmuxinator 让定义窗口和面板变得非常简单,而且也很容易让它们执行独立的命令。可以在每个窗口加载时指定它要执行的命令。让我们移除这些配置然后构建一个开发环境,在窗口上部打开 Vim,下面显示终端,初始化时进入 `~/devproject` 路径中。配置如下:
```
project_name: devproject
project_root: ~/devproject
windows:
- editor:
layout: main-horizontal
panes:
- vim
- #empty, will just run plain bash
- console: # empty
```
`yml` 文件格式使用的是 2 个空格作为缩进,所以你一定要确保在写入文件时保证文件里的缩进完全正确。
要使用新环境,我们保存刚才的配置文件然后执行下面的命令:
```
$ tmuxinator devproject
```
tmuxinator 就会自动地加载原始 `.tmux.conf` 文件,应用设置,然后使用刚才配置文件指定的窗口和面板设置。如果想再对环境有所修改,只需要再次执行这个命令:
```
$ tmuxinator open devproject
```
默认情况下,tmuxinator 的配置文件位于 `~/.tmuxinator/` 文件夹里,所以你可以在这里找到配置文件、备份配置文件,或是将配置文件与他人一起分享。
其实,tmuxinator 只是一个构造了一个脚本来执行独立的 tmux 命令,就像在之前在脚本文件里写的那样。它提供了一个非常漂亮又好记的语法。但是,要想使用 tmuxinator 你需要先安装一个 Ruby 解释器,所以它可能无法实现在任意环境下都使用 tmuxinator 来管理 tmux 的这种需求。
## 3.4 接下来做什么?
你可以在 shell 里使用 tmux 的每个命令,这就意味着你可以编写脚本来自动化 tmux 的几乎所有功能,包括运行会话等。例如,创建一个快捷键来运行一个 shell 脚本文件,这个脚本文件把当前的窗口分割为两个面板并把你的行为写入到 Web 和数据库的生产服务器的日志中。我们会在第 6 章讨论这些技术细节。
我们已经探讨了编写 tmux 脚本的 3 种不同方式,而且已经数次优化了配置。我们现在知道如何设置项目、在面板和窗口之间来回切换以及登录控制台。但是当我们在 tmux 会话里使用应用程序时,程序日志或者是测试结果会开始不停的刷屏。所以在接下来我们会学习如何处理输出缓冲区以及如何复制、粘贴文本。
### 以备查阅
#### 可脚本化的 tmux 命令
|命令 | 描述|
|---|---|
|`tmux new-session -s development -n editor`| 创建一个名为 development 的会话,将第一个窗口命名为 editor |
|`tmux attach -t development`| 连接到一个名为 development 的会话 |
|`tmux send-keys -t development '[keys]' C-m`| 向 development 会话的活动窗口或面板发送键盘命令,`C-m` 相当于按下回车键 |
|`tmux send-keys -t development:1.0 '[keys]' C-m `| 向 development 会话的第 1 个窗口和第 1 个面板发送键盘命令,`C-m` 相当于按下回车键 |
|`tmux select-window -t development:1`| 选择 development 会话的第 1 个窗口,让它作为活动窗口 |
|`tmux split-window -v -p 10 -t development`| 在 development 会话里垂直地分割当前窗口,把它分为水平的 2 个面板并设置它的高度为总窗口大小的 10% |
|`tmux select-layout -t development main-horizontal`| 设置 development 会话的布局为 main-horizontal |
|`tmux -f app.conf attach`| 加载配置文件 app.conf 并连接到由这个配置文件所创建的一个会话 |
|`tmuxinator open [name]`| 在项目名 [name] 路径下使用默认的编辑器打开配置文件。如果不存在就创建一个新的配置文件 |
|`tmuxinator [name]`| 对指定项目加载 tmux 会话。如果会话不存在或无法连接到会话就根据项目的配置文件创建一个新会话 |
|`tmuxinator list`| 列出当前的项目 |
|`tmuxinator copy [source] [destination]`| 复制一个项目配置文件 |
|`tmuxinator delete [name]`| 删除指定的项目 |
|`tmuxinator implode`| 删除当前所有的项目 |
|`tmuxinator doctor`| 使用 tmuxinator 和系统配置查找问题 |