ターミナルマルチプレクサ tmux をカスタマイズする

  • 666
    いいね
  • 0
    コメント

前提

ターミナルマルチプレクサとは、

  • GNU screen
  • tmux

などの仮想端末マネージャのことです。

メリット

  • マウスを使わず端末内でコピペできる(スクロールバックで端末画面外に流れてしまった情報にアクセスできる)
  • アタッチ・デタッチ機能で一時停止ができる
  • 画面分割により、一画面でコマンドラインとエディタを表示できる
  • 画面管理もできるので、すべてのウィンドウをタブのように管理できる
  • すべて設定ファイルでカスタマイズできる

ことが挙げられます。デメリットとしては、Vim や Emacs のように初期の導入コストが高いことでしょうか。これらの類はとっつきにくいイメージが有りますが、エディタのそれよりは簡単です。

参考

こんな感じ

これが普段使いの環境です。私の場合ですが、OS X 10.10 で利用し、インストールは Homebrew からです。ターミナル.app をフルスクリーンモードにして、その中で tmux から画面を分割したり束ねたりして利用しています。

$ tmux -V
tmux 1.9a

tmux.png

少し解説を入れます。

私はモバイル機・デスクトップ機ともに OS X なので、目の動きを同じにするためにステータスバーは上部に配置しています(これは Linux ユーザにも言えるかも)。

さらに、私は大画面で開発したいので、ターミナル.app をフルスクリーンモードで利用しているのですが、それゆえに OS X のステータスバーが隠れてしまいます。これでは、現在時刻や Wi-Fi やバッテリ残量なども確認できないので、専用スクリプトを書いて tmux 上のステータスバーに表示できるようにしています。

.tmux.conf
set-option -g status-right '#(wifi) #(battery --tmux) [%Y-%m-%d(%a) %H:%M]'
  • wifi

これは、Wi-Fi の SSID を取得するスクリプトです。ここからダウンロードし、実行権限(chmod 755 wifi)をつけてパスの通ったディレクトリ(例:/usr/local/bin)にインストールしてください。※現在、Mac のみ対応

  • battery --tmux

これは、バッテリー残量を取得するスクリプトです。残量を 時:分 と パーセンテージで表示します。ここからダウンロードし、wifi と同様にインストールしてください。battery はオプションなしで実行すると色はついていません。--tmux とすることで、tmux 用のカラーがオンになります。※Mac 完全対応、Linux 一部対応

  • :充電中
  • :20% 〜 100%(緑 100% になると Full charged と表示され青くなる)
  • :1% 〜 19%

3.png

4.png

5.png

ちなみに、ステータスバーを上部に配置できるようになったのは tmux 1.7 からです。

tmux_2.png

ステータスバー

上で挙げたカスタマイズ例を設定ファイルから見てみます。

.tmux.conf
# ステータスバーをトップに配置する
set-option -g status-position top

# 左右のステータスバーの長さを決定する
set-option -g status-left-length 90
set-option -g status-right-length 90

# #H => マシン名
# #P => ペイン番号
# 最左に表示
set-option -g status-left '#H:[#P]'

# Wi-Fi、バッテリー残量、現在時刻
# 最右に表示
set-option -g status-right '#(get_ssid) #(battery -c tmux) [%Y-%m-%d(%a) %H:%M]'

# ステータスバーを Utf-8 に対応
set-option -g status-utf8 on

# ステータスバーを1秒毎に描画し直す
set-option -g status-interval 1

# センタライズ(主にウィンドウ番号など)
set-option -g status-justify centre

カラースキーム

上記画像で見せた環境は Terminal.app 上で tmux、(zsh、)vim です。すべてカラースキームは Solarized で統一しています。Solarized は低コントラストで視認性もよく飽きがこないお勧めカラースキームです。

簡単にそれぞれの設定方法を紹介しておきます。

Terminal.app

上記リンク先から Dark または Light をダウンロードする。Command+, で環境設定を開き、「歯車マーク」>読み込む... から先ほどダウンロードした .terminal ファイルを読み込めばオッケー。

terminal.png

tmux

上記リンク先にカラースキームの dark と light があるので、好きな方を ~/.tmux.conf にコピペするだけです。

Vim

NeoBundle ユーザは以下を ~/.vimrc に記述するだけです。

.vimrc
NeoBundle 'altercation/vim-colors-solarized'
syntax enable
set background=dark    "または light
colorscheme solarized

プラグインマネージャを使用していない場合は以下です。

$ git clone https://github.com/altercation/vim-colors-solarized
$ cd vim-colors-solarized/colors
$ mv solarized.vim ~/.vim/colors/

そして、~/.vimrc に以下を記述します。

.vimrc
syntax enable
set background=dark    "または light
colorscheme solarized

コピーモード

tmux の魅力の一つのコピーモードです。コピーモードは端末上でマウス操作を一切なしでコピペできる優れ機能です。また、デフォルトでは 2000 行までのスクロールバック(端末画面外に消えてしまった出力)をさかのぼれます(Ctrl-fCtrl-b)。

copy-mode.gif

このコピーは tmux のバッファスタックに保存されます。つまり、OS のクリップボードと連携できません。では、さっそく連携させましょう。

.tmux.conf
# Vi キーバインド
set-window-option -g mode-keys vi

# Prefix+v でコピーモード開始
bind-key v copy-mode \; display "Copy mode!"
#bind-key -t vi-copy v begin-selection

# Prefix+Enter/y でコピー
bind-key -t vi-copy Enter copy-pipe "reattach-to-user-namespace pbcopy"
bind-key -t vi-copy y     copy-pipe "reattach-to-user-namespace pbcopy"

# Prefix+p でペースト
# クリップボードにも保存されているので Cmd-v でもペースト可能
bind-key p paste-buffer

これは Enter/y 押下時に reattach-to-user-namespace pbcopy を実行しています。pbcopy は OS X ターミナル上からクリップボードを利用するコマンドですね。reattach-to-user-namespace はそれの橋渡し的存在です。Homebrew ユーザなら、

brew install reattach-to-user-namespace

で OK です。

ちなみに copy-pipe は tmux 1.8 から利用可能です。それ以前はとてもややこしい方法で実現させていました

また、Vim とでも tmux のコピペができると便利なので連携させましょう(参考:tmuxとMacのクリップボードを共有する(copy-mode, vim))。

.tmux.conf
# vim <=> tmux 間でクリップボード利用を可能にする
set-option -g default-command "reattach-to-user-namespace -l $SHELL"

以前はスクロールバック用のモードがあったのですが、今はコピーモードに統合されました。なので、コピーモードのキーバインドをカスタマイズすれば、カーソル移動やページめくりが便利になります。

.tmux.conf
# Esc キーでコピーの反転を解除(コピーモードは抜けない)
bind-key -t vi-copy Escape clear-selection
# Ctrl-c キーでコピーの反転を解除し、コピーモードから抜ける
bind-key -t vi-copy C-c   cancel

# コピーモード中(Prefix+v 後)C-v で矩形選択開始
bind-key -t vi-copy C-v   rectangle-toggle
# 1行選択
bind-key -t vi-copy V    select-line

# Vi モード中に Ctrl-a で行頭に(Emacs ライク)
bind-key -t vi-copy C-a   start-of-line
# Vi モード中に Ctrl-e で行末に(Emacs ライク)
bind-key -t vi-copy C-e   end-of-line

# 単語の最初の1文字に移動
bind-key -t vi-copy w    next-word
# 単語の最後の1文字に移動
bind-key -t vi-copy e    next-word-end
# w の逆の動き back
bind-key -t vi-copy b    previous-word

# 画面上に映る最上行に移動
bind-key -t vi-copy g    top-line
# 画面上に映る最下行に移動
bind-key -t vi-copy G    bottom-line

# 前方検索
bind-key -t vi-copy /    search-forward
# 後方検索
bind-key -t vi-copy ?    search-backward

# ページスクロール
bind-key -t vi-copy C-n   page-up
bind-key -t vi-copy C-f   page-down
# ページ送り
bind-key -t vi-copy C-u   scroll-up
bind-key -t vi-copy C-d   scroll-down

ペイン・ウィンドウ操作

tmux はペインでウィンドウを分割でき、開発内容や案件ごとにウィンドウを保持し、サーバーごとにセッションを管理できます。

ペイン ウィンドウ セッション クライアント
ウィンドウを分割した領域
縦分割、横分割
タブのようなもの
ペインを管理する領域
ウィンドウの集まり
を総称したもの
tmux を起動している
ターミナルや端末のこと

tmux_words.jpg

これらの切り替えをスムーズにする設定をしましょう。ペインやウィンドウの切り替えは vi ライクなキーバインドにしています。コピーモードでも vi ライクなものにしているので統一しておくのがいいでしょう。

.tmux.conf
# ウィンドウとペインの番号を1から開始する(デフォルト0)
set-option -g base-index 1
set-window-option -g pane-base-index 1

# Prefix+- で横に、Prefix+| で縦に分割(ペイン)する
bind-key | split-window -h
bind-key - split-window -v

# Prefix + Ctrl-h/l でウィンドウ切り替え
# Prefix + Ctrl-h,h,h,h,...と連打できる
bind-key -r C-h select-window -t :-
bind-key -r C-l select-window -t :+

# Prefix+hjkl でペイン移動
bind-key h select-pane -L
bind-key j select-pane -D
bind-key k select-pane -U
bind-key l select-pane -R

# ペインサイズを上下左右(Prefix+JKHL)に変更
# Prefix+J,J,J,J,...と連打してリサイズ可能
bind-key -r H resize-pane -L 5
bind-key -r J resize-pane -D 5
bind-key -r K resize-pane -U 5
bind-key -r L resize-pane -R 5

# Ctrl-o でペインをローテーションしながら移動
# Prefix を用いないのでタイプが楽だが、Ctrl-o を使用してしまう
# 他のソフトウェアの設定に支障をきたさないように注意
bind-key -n C-o select-pane -t :.+

シェルと連携する

さあ、ここまで来ると便利さがわかってきた tmux。しかし、このままではシェルとの連携ができていません。つまり、シェルにログインした時(ターミナル.app を再起動した時など)、いちいち

$ tmux

としないと、tmux に入れません。これでは非常に面倒なので、シェルにログインした時にデタッチされたセッションに自動にアタッチ出来るようにしましょう。tmux では Prefix+d で明示的にデタッチもできますが、端末が終了したとき(ログインシェルで exit した時や、Command+q でターミナル.app を終了した時)にも自動でデタッチされます。

よって、シェルにログインした時に、デタッチされたセッションがあればアタッチし、なければ tmux new-session を実行するようにしてみましょう。

.zshrc
function is_exists() { type "$1" >/dev/null 2>&1; return $?; }
function is_osx() { [[ $OSTYPE == darwin* ]]; }
function is_screen_running() { [ ! -z "$STY" ]; }
function is_tmux_runnning() { [ ! -z "$TMUX" ]; }
function is_screen_or_tmux_running() { is_screen_running || is_tmux_runnning; }
function shell_has_started_interactively() { [ ! -z "$PS1" ]; }
function is_ssh_running() { [ ! -z "$SSH_CONECTION" ]; }

function tmux_automatically_attach_session()
{
    if is_screen_or_tmux_running; then
        ! is_exists 'tmux' && return 1

        if is_tmux_runnning; then
            echo "${fg_bold[red]} _____ __  __ _   ___  __ ${reset_color}"
            echo "${fg_bold[red]}|_   _|  \/  | | | \ \/ / ${reset_color}"
            echo "${fg_bold[red]}  | | | |\/| | | | |\  /  ${reset_color}"
            echo "${fg_bold[red]}  | | | |  | | |_| |/  \  ${reset_color}"
            echo "${fg_bold[red]}  |_| |_|  |_|\___//_/\_\ ${reset_color}"
        elif is_screen_running; then
            echo "This is on screen."
        fi
    else
        if shell_has_started_interactively && ! is_ssh_running; then
            if ! is_exists 'tmux'; then
                echo 'Error: tmux command not found' 2>&1
                return 1
            fi

            if tmux has-session >/dev/null 2>&1 && tmux list-sessions | grep -qE '.*]$'; then
                # detached session exists
                tmux list-sessions
                echo -n "Tmux: attach? (y/N/num) "
                read
                if [[ "$REPLY" =~ ^[Yy]$ ]] || [[ "$REPLY" == '' ]]; then
                    tmux attach-session
                    if [ $? -eq 0 ]; then
                        echo "$(tmux -V) attached session"
                        return 0
                    fi
                elif [[ "$REPLY" =~ ^[0-9]+$ ]]; then
                    tmux attach -t "$REPLY"
                    if [ $? -eq 0 ]; then
                        echo "$(tmux -V) attached session"
                        return 0
                    fi
                fi
            fi

            if is_osx && is_exists 'reattach-to-user-namespace'; then
                # on OS X force tmux's default command
                # to spawn a shell in the user's namespace
                tmux_config=$(cat $HOME/.tmux.conf <(echo 'set-option -g default-command "reattach-to-user-namespace -l $SHELL"'))
                tmux -f <(echo "$tmux_config") new-session && echo "$(tmux -V) created new session supported OS X"
            else
                tmux new-session && echo "tmux created new session"
            fi
        fi
    fi
}
tmux_automatically_attach_session

上記コードを ~/.bashrc なり、~/.zshrc なりに貼り付ければいいです(それ以外のシェルでは動作未確認)。

では簡単にどんな動きをするか解説します。

  • ターミナルマルチプレクサが既に動いていたら、その旨を出力する(tmux に関しては AA を表示している)
  • screen や tmux 上でなく、ssh 接続先でもなくインタラクティブシェルとして起動されたログインシェルなら以下に分岐する
    • デタッチされたセッションがある場合
      • y または Enterが入力された場合:最後にいたセッションにアタッチする
      • 数字が入力された場合:その数字のセッション番号のセッションにアタッチする
      • それ以外が入力された場合:新規セッションを作成
    • デタッチされたセッションがない場合
      • 新規セッションを作成

attach.png

とりあえず、最後のセッションにアタッチしたけりゃ Enter で問題無いです。

最後に

ターミナルマルチプレクサはターミナル生活を豊かにします。今回は tmux を題材にその周辺環境を含めて紹介してみました。本記事で載せた tmux に関するコードはすべてここにあります。コピペして ~/.tmux.conf に配置し再読み込みすれば利用できるはずです。需要があれば以下の設定方法も追記しようと思います。

  • ログイン時に tmux 自動起動されるが、これに自動画面分割の処理も加える
  • 別のペイン/ウィンドウのカレントディレクトリに移動する

SEE ALSO

b4b4r07/dotfiles - GitHub

vim_on_zsh_on_tmux.png