# 第 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` 命令把它放回原處。這個操作做起來有些繁瑣,因此我們編寫一個腳本來實現這個功能。這是[鏈接](http://superuser.com/questions/238702/maximizing-a-pane-in-tmux)。
首先,我們釋放 `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 會話中,一如既往。