SSH接続先のホスト名をtmuxのペインに表示する(大事件)

rec_for_qiita_20180322_2.gif

前置き

最近書いた,これでも言ったけれど,
(tmuxのペインのステータスラインにgitのブランチとかディレクトリとか表示する(プロンプトはもう古い))

とりあえず,何より大事なのは,tmuxではペインごとにステータスラインが表示できるようになったという事実である.これは本当に大事件である.

2年前ぐらいから、"tmuxってssh接続とかたくさんやるとどれがどのホストにログインしてんだかワカンネ〜〜ナ〜〜"とは思っていたので,すでに表題のアイディア自体はあった.だから上の記事を書く時にもそれは考えていたんだけど,なかなか実装が思いつかなかった,が,今日,とりあえず突貫工事ではあるがそれっぽいものはできたので書く.

いろいろ言っといてアレだが,僕はインフラエンジニアを名乗れるほどこの分野に明るくないので,何かしらの不備はあるかもしれない,そういう時は編集リクエストなりコメントなり送ってくれると非常にありがたい.

機能

スクリーンショット 2018-03-22 06.12.38.png
tmuxのセッション内でssh でログインすると,ログイン先のusernameとhostnameをtmuxのペインのステータスラインに表示する.それだけ.

上のスクリーンショットのvagrant@192.168.33.10がその部分.(その左の0はペインのインデックス)

実装

まず,.tmux.conf
機能の本質ではない色とかそういうやつは省いて書く.
以下のコードを.tmux.confにコピペ.

.tmux.conf
set-option -g pane-border-status bottom
set-option -g pane-border-format "#P #(tmux-pane-border #{pane_current_command} #{pane_pid})"

次に,tmux-pane-border,これは自作コマンドで,別に名前はなんでもいい.ご自分でファイルを作り,パスの通ったディレクトリに置き,実行属性を付与するなどしてコマンドとして使える状態にしていてください.「tmuxのペインのステータスラインにgitのブランチとかディレクトリとか表示する(プロンプトはもう古い)」の方の記事で既にtmux-pane-borderコマンドを作っている方は後述の設定がオススメです.
(.zshrcにシェル関数として書いておいても動くか,と思ったが.tmux.confからは呼び出せなかった.)

tmux-pane-border
#!/bin/zsh

if [[ $1 = "ssh" ]]; then
  pane_pid=$2
  info=$({ pgrep -flaP $pane_pid ; ps -o command -p $pane_pid; } | xargs -I{} echo {} | awk '/ssh/' | sed -E 's/^[0-9]*[[:blank:]]*ssh //')
  port=$(echo $info | grep -Eo '\-p ([0-9]+)'|sed 's/-p //')
  if [ -z $port ]; then
    local port=22
  fi
  info=$(echo $info | sed 's/\-p '"$port"'//g')
  user=$(echo $info | awk '{print $NF}' | cut -f1 -d@)
  host=$(echo $info | awk '{print $NF}' | cut -f2 -d@)

  if [ $user = $host ]; then
    user=$(whoami)
    list=$(awk '
      $1 == "Host" {
        gsub("\\\\.", "\\\\.", $2);
        gsub("\\\\*", ".*", $2);
        host = $2;
        next;
      }
      $1 == "User" {
      $1 = "";
        sub( /^[[:space:]]*/, "" );
        printf "%s|%s\n", host, $0;
      }' ~/.ssh/config
    )
    echo $list | while read line; do
      host_user=${line#*|}
      if [[ "$host" =~ $line ]]; then
        user=$host_user
        break
      fi
    done
  fi
  ssh_hostname=" ssh:$user@$host "
fi

echo $ssh_hostname

解説

いじって見ればわかるが,.tmux.confpane-border-formatの動きはwindow-status-formatとはだいぶ違う動き方をする.その辺の動き方がコードを煩雑にしている感がある(#{pane_current_command}とかを引数で渡すのが気持ち悪すぎる).どう違うのかは説明がめんどくさいので割愛.

全体の実装を簡単にいうと,tmuxから渡されるペインのpidを元にホスト名とかを探している感じ.

よりオススメの設定

上のgifでは,ssh接続をする前にはディレクトリやブランチが表示されているのがわかるだろうか.
これは「tmuxのペインのステータスラインにgitのブランチとかディレクトリとか表示する(プロンプトはもう古い)」の設定をssh接続しているかどうかで切り替えているからこういう挙動になっている.
ssh接続している時はペインのステータスラインにホスト名,していない時はカレントディレクトリやgitの情報を表示する,ということだ.

tmux-pane-border
#!/bin/zsh

if [[ $1 = "ssh" ]]; then
  pane_pid=$2
  info=$({ pgrep -flaP $pane_pid ; ps -o command -p $pane_pid; } | xargs -I{} echo {} | awk '/ssh/' | sed -E 's/^[0-9]*[[:blank:]]*ssh //')
  port=$(echo $info | grep -Eo '\-p ([0-9]+)'|sed 's/-p //')
  if [ -z $port ]; then
    local port=22
  fi
  info=$(echo $info | sed 's/\-p '"$port"'//g')
  user=$(echo $info | awk '{print $NF}' | cut -f1 -d@)
  host=$(echo $info | awk '{print $NF}' | cut -f2 -d@)

  if [ $user = $host ]; then
    user=$(whoami)
    list=$(awk '
      $1 == "Host" {
        gsub("\\\\.", "\\\\.", $2);
        gsub("\\\\*", ".*", $2);
        host = $2;
        next;
      }
      $1 == "User" {
      $1 = "";
        sub( /^[[:space:]]*/, "" );
        printf "%s|%s\n", host, $0;
      }' ~/.ssh/config
    )
    echo $list | while read line; do
      host_user=${line#*|}
      if [[ "$host" =~ $line ]]; then
        user=$host_user
        break
      fi
    done
  fi
  ssh_hostname=" ssh:$user@$host "
  git_info=""
  directory=""
else
  if git_status=$(cd $3 && git status 2>/dev/null ); then
    git_branch="$(echo $git_status| awk 'NR==1 {print $3}')"
    case $git_status in
      *Changes\ not\ staged* ) state="#[bg=colour013,fg=black] ± #[fg=default]" ;;
      *Changes\ to\ be\ committed* ) state="#[bg=blue,fg=black] + #[default]" ;; 
      * ) state="#[bg=green,fg=black] ✔ #[default]" ;;
    esac
    if [[ $git_branch = "master" ]]; then
      git_info="#[underscore]#[bg=black,fg=blue] ⭠ ${git_branch} #[default]${state}"
    else
      git_info="#[underscore]#[bg=black,fg=colour014] ⭠ ${git_branch} #[default]${state}"
    fi
  else
    git_info=""
  fi
  ssh_hostname=""
  directory="$3"
fi

echo "#[bg=colour013,fg=black]$ssh_hostname#[default]#[bg=black,fg=cyan]#[underscore]$directory#[default]$git_info"

.tmux.conf
set-option -g pane-border-format "#[fg=black,bg=blue] #P #[default]#(tmux-pane-border #{pane_current_command} #{pane_pid} #{pane_current_path})"
.zshrc
function precmd() {
  if [ ! -z $TMUX ]; then
    tmux refresh-client -S
  fi
}

上の設定をすると,今までプロンプトに表示していたディレクトリなどの情報が全く無駄になることに気づくだろう(あるいはもともとのがあるから上の設定はいらない,となるかもしれないが).そうなると,.zshrcなどに記述していたプロンプトの設定を削除したくなるかもしれない.現に僕はそうした.
しかし,人類がtmuxの上でしか生活しなくなってからずいぶん長い時間が経ったというものの,やはり素のターミナルを扱う場面においてカレントディレクトリなどの情報がないのは困る.そこで,tmuxの内外でプロンプトの表示をきりかえるとよい.参考までに僕の.zshrcのその部分を載せておく.

.zshrc
#前略

function precmd() {
  if [ ! -z $TMUX ]; then
    tmux refresh-client -S
  else
    dir="%F{cyan}%K{black} %~ %k%f"
    if git_status=$(git status 2>/dev/null ); then
      git_branch="$(echo $git_status| awk 'NR==1 {print $3}')"
       case $git_status in
        *Changes\ not\ staged* ) state=$'%{\e[30;48;5;013m%}%F{black} ± %f%k' ;;
        *Changes\ to\ be\ committed* ) state="%K{blue}%F{black} + %k%f" ;;
        * ) state="%K{green}%F{black} ✔ %f%k" ;;
      esac
      if [[ $git_branch = "master" ]]; then
        git_info="%K{black}%F{blue}⭠ ${git_branch}%f%k ${state}"
      else
        git_info="%K{black}⭠ ${git_branch}%f ${state}"
      fi
    else
      git_info=""
    fi
  fi
}

#後略

その他

・tmuxのアタッチとかセッションの管理を奇跡的に簡略化するtmuximumというプラグインがある,是非使ってほしい.

tmuxのステータスバーにssidとかバッテリーとか音量とかload averageとか出す方法もあるらしい,是非みてほしい

・やはりtmuxは無限の可能性を秘めていると思った.

tmuxの公式のマニュアルを読むと知らないことがいっぱい書いてある,様々な発見があったので読んでみてほしい.

・最後に,コードはsoyuka/tmux-current-pane-hostnameを大いに参考にした.これと違うのはペインのステータスラインに表示するかウィンドウのステータスラインに表示するかだけであるが,この違いはかなり大きいと思う.

・License:MIT

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.