byobuで利用されているtmuxは、非常に多くのカスタマイズが可能です。今週はtmuxのカスタマイズを進め、より多くの機能を実現できるようにしてみましょう。なお、tmuxのカスタマイズは、「 本来やるべきことを忘れて設定の修正に専念してしまう」という病気を発症する可能性があります。とくに、emacs.elや.vimrc、.zshrcなどに時間を費やしたことのある方はかなり危険なので、なるべく時間を確保してから触ることをお勧めします。
byobu環境との親和性を保つ
byobu(tmuxバックエンド)環境でtmux的なカスタマイズを行う場合、~/.byobu/.tmux.confを編集します。通常のtmuxが利用するファイルは~/.tmux.confなので、競合する設定を投入することも可能です。とは言え、最も簡単なのは、~/.byobu/.tmux.confをbyobu・tmuxそれぞれを壊さない形で編集し、~/.tmux.confはシンボリックリンクにしておくことです。基本的にbyobu側は.tmux.confの設定を尊重するように動作するので、「 ~/.byobu/.tmux.confが素のtmux環境でも問題なく動作すること」さえ気にかければ大丈夫です。
GNU Screenからの乗り換え
tmuxがいくら便利そうでも、手がGNU Sreenに慣れていて乗り換えられない、ということもあるでしょう。このような場合は、tmuxの設定をできるだけGNU Screenに近づけて利用するのが良いでしょう。
コピーモードのキーバインド
tmuxには、GNU Screenユーザが乗り換えるための設定ファイルとして、第127回 でも紹介されているように、/usr/share/doc/tmux/examples/screen-keys.confに一種のプリセットが用意されています。これを.tmux.confにコピーすることで、基本的な操作、とくにウィンドウの作成や移動は一通りGNU Screen互換にすることができます。なお、tmuxバックエンドのbyobuでは、これに相当する設定がすでに投入されています。
しかし、この方法で実現できるGNU Screenとの互換性は、コピーモードでは機能しません(コピーモードに入るにはエスケープシーケンス+「[」か、エスケープシーケンス+「Esc」を用います) 。
Screenでは、コピーモードに入ると、カーソルの移動にEmacsライク・viライクなキーバインドが組み合わさったものが利用できます。hjklとCtrl+npfbのどちらもが有効で、Emacsとvi系エディタを併用しているユーザにとってはその日の気分で操作を使い分けることができ、非常に重宝します。しかしtmuxでは「vi風味」と「Emacs風味」のどちらかから選択するしかありません。hjklとCtrl+npfbを適当に 使い分ける人にとっては死活問題です。byobuのキーバインドも同じで、GNU Screenに慣れきったユーザにとっては、「 同じbyobuとはいえ細かなキーバインドが違う謎のインターフェース」という状態になっています。
このような場合、キーバインドをカスタマイズしておくと違和感なく乗り換えることができます。
まず、おおもととなるキーバインドを選択します。ここではvi風の「vi」を選択しています。
set-window-option -g mode-keys vi
Emacs風味のカーソルキー移動設定を追加します。bind-key -t vi-copyは、「 vi-copy環境のキーバインド」に対する設定を意味します。上記の「mode-keys vi」はコピーモードでvi-copyバインドを利用することを指示しています。
bind-key -t vi-copy C-n cursor-down
bind-key -t vi-copy C-p cursor-up
bind-key -t vi-copy C-f cursor-right
bind-key -t vi-copy C-b cursor-left
これにより、GNU Screenで利用できるEmacs風+vi風キーバインドに近い、「 hjklでもC-npfbでも操作できる」環境ができました。とはいえ、C-bは画面のスクロールで利用することも多いため、カーソルの上下移動だけ(つまり以下の設定だけ)に留め、後半2行はコメントアウトしておいても良いでしょう。tmuxの設定ファイルでは、「 #」以降がコメントとみなされます。
bind-key -t vi-copy C-n cursor-down
bind-key -t vi-copy C-p cursor-up
#bind-key -t vi-copy C-f cursor-right
#bind-key -t vi-copy C-b cursor-left
ただし残念ながら、現在のtmuxの実装ではGNU Screenと完全に同じキーバインドを実現することはできません。コピーにかかわる操作を一致させることができないためです。GNU Screenのデフォルトでは、コピーの開始と終了を、どちらもスペースキーに割り当てます。スペースキーでコピーを開始し、必要な範囲を選択したらもう一度スペースキーを押すとバッファに保存される、というものです。tmuxでは、コピーの開始と終了を別のキーに割り当てる必要があります[1] 。
[1] キーバインドに対して複数のコマンドを割り当てることができれば、後述するステータスバーと同じようにループするキーバインドを行うことでこの問題を回避することもできるのですが、vi-copy/emacs-copyでは1つのコマンドしか割り付けられないため、「 スペースキーで選択して、再度スペースキーで選択終了」という操作を実現させる方法がありません。
コピーモード切替のメッセージ
GNU Screenでは、コピーモードに入ると、画面の左下に「Copy mode」といったインジケータが表示されます。長年Screenを利用していると、コピーモードに切り替えるためのキー操作をする→画面に「Copy mode」と表示されることを確認する→コピーモード特有の操作を始める、というクセがついてしまっているはずです。
図1 Screenのコピーモード表示
tmuxの場合、コピーモードに入っているか否かは、画面右上の「0/1000」といった表示を確認する必要があります。
図2 tmuxのコピーモード表示
視認に利用する場所が左下と右上でまったく異なるため、キー操作をする→インジケータが表示されないのでもう一度→やはり表示されないのでもう一度→何かおかしいことに気づく→tmuxを使っていることを思い出して画面の右上に視線を向ける、などといった悲しい光景を繰り広げることになります。これは、乗り換えにあたって大きな問題になります。この問題を回避するには、次のような設定を加えます。
bind-key [ copy-mode \; display "(Copy mode)"
bind-key Escape copy-mode \; display "(Copy mode)"
この設定を加えることで、コピーモードに切り替えたときに、画面下部に切り替えメッセージが表示されるようになります。これは、bind-keyでキーバインドを設定する際、「 コピーモードに切り替えるためのコマンド(「 copy-mode」 )に、「 ;」で区切って「display」( メッセージ出力)を呼び出す」というものを割り当てています。
単に「;」で区切るとキーバインド設定ではなく、「 コピーモードのキーバインド設定を行い、その後でdisplayする」という意味になってしまうので、「 \」でエスケープして「コピーモードに切り替え、displayするキーバインド設定」として認識させる必要がある、というのが少々特殊です。
図3 画面下部にメッセージが表示されるようになった
この設定例では、GNU Screenの標準的なキーバインド(エスケープシーケンス後に「]」か「ESC」でコピーモード)を前提にしているため、標準と異なるキーバインドを用いる場合は変更する必要があります。
利用している環境のキーバインドに合わせて設定を調整してください。キーバインドを確認するには、エスケープシーケンスを入力した後に「:」を押してコマンド入力モードにした後で「list-keys」を実行するか、通常のUNIXコマンドとして「tmux list-keys」を実行してください。
なお、メッセージ(display)の表示時間や色は、次の設定で制御することができます。
set-option -g display-time 800
set-option -g message-bg green
set-option -g message-fg white
この例では、表示時間を800ms、表示の背景色を緑、文字色を白にしています。
ウィンドウ分割コマンド
GNU Screenライクなキーバインドはこのあたりにして、tmuxに便利な設定を追加してみましょう。
tmuxには、1枚のウィンドウを複数のペインに分割する機能があります。次の例では、ウィンドウを縦線によって2つに分割し、左側では通常のシェルを、右側ではtopを実行しています。
図4 左側にシェル、右側にtop
tmuxのウィンドウの操作は少々覚えにくいので、なんらかのキーバインドを割り当ててしまうことをお勧めします。以下は筆者が利用している設定です。Emacs風に、エスケープシーケンス+2で横線による分割、+3で縦線による分割、エスケープシーケンス+oでペイン間を移動、エスケープシーケンス+1で元に戻す、というものです。
unbind 1
bind-key 1 break-pane \; display-panes
unbind 2
bind-key 2 split-window -v \; display-panes
unbind 3
bind-key 3 split-window -h \; display-panes
bind-key -r C-o select-pane -t :.+ \; refresh-client
bind-key -r o display-panes \; select-pane -t :.+ \; refresh-client
unbind C-k
bind-key C-k confirm kill-pane
unbind K
bind-key K confirm kill-pane
この設定では、Emacs風のウィンドウ分割操作(=split-windowやbreak-paneの呼び出し)に加えて、display-panes(分割されたペインの番号を表示するコマンド)を呼び出すことで、ウィンドウが分割されたことが目立つようになっています。また、「 C-k」でペイン単位に削除するようにキーバインドを割りつけています。tmuxではこの操作はウィンドウ単位で消えてしまいますので、少し動作を後退させたことになります。
また、ウィンドウを分割してからコマンドを実行するのは面倒だ、という場合、次の設定を追加しておくと良いでしょう。
bind-key t command-prompt -I "ssh " -p "new:" "new-window -n '(%1)' '%%'"
bind-key T command-prompt -I "ssh " -p "sub(|):" "split-window -h 'exec %%'"
bind-key C-T command-prompt -I "ssh " -p "sub(-):" "split-window -v 'exec %%'"
この例では次のようにt/T/C-tに動作を割り振っていますが、C-1/C-2/C-3を割り当てたくなるのですが、残念ながらターミナル上ではCtrlキーとの組み合わせはアルファベットにしか機能しません。!/@/#や!/"/#を割り当てる、という手もありますが、今度は設定ファイルを見たときに「このシーケンスはいったい何だろう、わけわかんないぞ」とばかりに削除しかねないので、あまりお勧めしません。
エスケープシーケンス+t:新しいウインドウを作成してコマンドを実行
エスケープシーケンス+T:縦線で分割してコマンドを実行
エスケープシーケンス+C-t:横線で分割してコマンドを実行
この設定は、「 コマンド名をプロンプトから対話的に受け取り、受け取ったコマンド名を新しいウィンドウ(もしくは既存のウィンドウを分割したもの)で開く」というものです。実行すると次のように、画面最下部にプロンプトが展開されます。sshの接続先等を入力すると、自動的に新しいウィンドウが開く(あるいは分割される)ことになります。
図5 画面最下部のプロンプト
また、プロンプトには初期値(-I)として「ssh 」が与えられていますが、これを削除して別のコマンドを実行することもできます。以下の例ではtopを実行しています。
図6 ウインドウが横線で分割された
こうして開いたウィンドウはシェルを介在しないので、remain-on-exitがoffの場合は、コマンドが終了した段階でペインごと削除されます。ちょっとした状況確認に便利です[2] 。ただし、標準出力に結果を吐いて終了するコマンドの場合、単に実行すると一瞬でウインドウが閉じてしまい、内容を確認することができません。lessなどにパイプ経由で流しこむのが良いでしょう。
同様に、次のようなキーバインドをマッピングしておくと、画面の下半分に気軽にmanを表示することもできます。
もちろん終了するとペインが閉じられる「使い捨て」の特性もそのままです。コマンド実行時に気軽にmanを引くクセのない人にもお勧めします。
bind-key C-m command-prompt -p "man:" "split-window -v 'exec man %%'"
図7 画面下部で使い捨てのmanページを開く
さらに、次の設定を加えておくと、「 コマンドをバックグラウンドで実行する」という機能を実現できます。
bind-key r command-prompt -p "(run-shell)" "run-shell '%%'"
実行されたコマンドはバックグラウンドで走り続け、終了した時点でtmuxのバッファ(コピーモードで入るのと同じ操作の専用バッファ)に展開されます。長時間かかるコマンドには不向きですが、ちょっとしたコマンドの出力を確認したい場合に便利です。
図8 バックグラウンドで実行したコマンド
ファイルの貼り付け
tmuxを使うと、「 ローカルのファイルをリモートに貼り付ける」ということもできます。次の設定を.tmux.confに加えてください。
bind-key i command-prompt -p "(load-and-paste) file?" "load-buffer %%" \; paste-buffer
bind-key I command-prompt -p "(load-buffer) file?" "load-buffer %%"
この設定が実現する動作は、vi系エディタの「:r」 、Emacsの「C-x i」に相当します。指定されたファイルを、tmuxのウィンドウ上に流し込みます。SSH等でリモートマシンに接続している場合も機能するので、設定ファイルの流し込みに便利です。たいていの場合、リモートマシン側でvimを起動して、:set pasteしてから貼り付けることになるでしょう。一度コピーしてからエスケープシーケンス→「 ]」で流しこむこともできますが、直接ローカルにあるファイルを指定できるのがメリットです。
なお、ファイルを相対パスで指定する場合、tmuxが起動されたカレントディレクトリが基準になるので注意する必要があります。
独自のステータス表示
ある程度以上にtmuxの設定を増やしてしまうと、byobuが利用できない環境(=Ubuntu以外の環境)にも設定を持ち出したくなります。byobuは実際には多くのUNIX環境にポーティングされているのですが、byobuのバージョンが古くtmuxバックエンドが利用できない、あるいはtmuxはあるがbyobuが存在しない、といった環境に遭遇することもあります。
このような場合に備えて、自分でステータスバーを構築しておく、というのも手です。筆者は次のような設定を利用しています。この設定は、.tmux.confに設定すべきものに加えて、~/.tmux.conf.status1と~/.tmux.conf.status2という別ファイルに分離してあります。「 エスケープシーケンス+l」が押されるたびにstatus1かstatus2がトグルで表示が切り替わります。
STATUS_LEFT_SIMPLE="#[fg=black,bg=$MYHOSTCOLOR] #h #[fg=brightcyan,bg=black][#S:#I:#P]#[default]"
STATUS_LEFT_DETAIL="#[fg=green,bg=black] #(lsb_release -c --short) #[fg=black,bg=$MYHOSTCOLOR] #h #[default]#[fg=black,bg=yellow]
.tmux.conf.status1
set -g status-left $STATUS_LEFT_SIMPLE
set -g status-right $STATUS_RIGHT_SIMPLE
bind-key l refresh-client \; source ~/.tmux.conf.status2
# vim syntax sugars{{{1
# vim:set ft=tmux ts=2 sw=2 sts=2 foldmethod=marker:
# }}}
.tmux.conf.status2
set -g status-left $STATUS_LEFT_DETAIL
set -g status-right $STATUS_RIGHT_DETAIL
bind-key l refresh-client \; source ~/.tmux.conf.status1
# vim syntax sugars{{{1
# vim:set ft=tmux ts=2 sw=2 sts=2 foldmethod=marker:
# }}}
なお、ホストごとに自動的に配色を変更するため、「 $MYHOSTCOLOR」という環境変数を背景色として利用します。$MYHOSTCOLORは~/.bashrc・~/.zshrcなどで明示的に宣言するか、たとえば次のような設定を~/.zshrcに加えて、ホスト名に応じて自動的に決まるようにしておいても良いでしょう。
alias str2num="hexdump -v -e '8/1 \"%03o\" \"\n\"'"
alias host2color="echo $[`hostname | str2num -n5` % 8]"
MYHOSTCOLOR="$((`host2color`+2))"
また、status1/status2等の設定ファイルを増やせば、トグル動作からサイクル動作に変更することもできます。
複数ペインへの同時入力
tmuxには、複数のペインに同じ入力を行う機能があります。Cluster SSH(cssh)や、TeraTermなどに備わっているものとほぼ同等の機能で、複数のサーバに対して同時に設定を行いたい(が、そのためのツールなりワンライナーを準備するほどのことでもない)場合に威力を発揮します。
たとえば次のように設定しておくと、「 e」で複数ペインへの同時入力を開始し、「 E」で同時入力を終了します。ステータスバーと同じ方法を使うと簡単にトグル動作も実現できますが、この種の大きな副作用を持った操作の場合、キーバインドはモードレスにしておく(つまり、「 エスケープシーケンス+C-Eすれば必ず終了する」ということが保証されている状態にしておく)べきです。
set-option -g synchronize-panes off
bind-key e set-window-option synchronize-panes on \; display 'synchronize begin'
bind-key E set-window-option synchronize-panes off \; display 'synchronize end'
このモードはかなり危険なので、カスタムステータスバーの場合は背景色が変わるように設定しても良いでしょう。次のように記述しておきます。
set-option -g synchronize-panes off
bind-key e set-window-option synchronize-panes on \; set-option -g status-bg red \; display 'synchronize begin'
bind-key E set-window-option synchronize-panes off \; set-option -g status-bg $MYHOSTCOLOR \; display 'synchronize end'
なお、この原稿時点での.tmux.confがhttps://github.com/fumihito/dotfiles/tree/gihyo.jp にあります。面倒な方はこれを使ってカスタマイズされたtmuxを試してみても良いでしょう。ただし、$MYHOSTCOLORだけは(.bashrcなりで)セットすることをお忘れなく。なお、紹介していないキーバインドも幾つか転がっていますので、実際に触る前に「エスケープシーケンス→C-?」でキーバインド一覧を眺めておくことをお勧めします。