SSH の接続先のホスト名を tmux に表示する方法は何人かの方が公開されていますが、設定や手順が複雑化しがちだったので、プラグイン化してみました。
プラグインは tmux 用プラグインマネージャーの TPM から呼び出すことを想定して作っています。
このプラグインを導入すると、window-status-format や window-status-current-format に #{ssh-host} と書くことで、ペインで起動されている ssh コマンドの、接続先のホスト名が表示できるようになります。
具体的な設定例はこんな感じ。
set-window-option -g window-status-format '#I: #{?#{m:ssh,#W},#{ssh-host},#W} '
set-window-option -g window-status-current-format '#I: #{?#{m:ssh,#W},#{ssh-host},#W} '
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'yuuan/tmux-ssh-host'
run -b '~/.tmux/plugins/tpm/tpm'
前半の set-window-option のところが、実際にこのプラグインを利用するための設定になります。
#{? から始まる書式は、if 文のようなもので、#{?<条件式>,<真の場合の値>,<偽の場合の値>} という意味になります。1
また、#{m: から始まる書式は、#{m:<パターン>,<文字列>} と書くことで、文字列がパターンにマッチするかどうかを返します。2
この場合、#W がパターン ssh にマッチする場合は #{ssh-host} を、そうでなければ #W を表示するように設定しています。
後半は TPM でプラグインを読み込むための設定 です。
実装方法の説明
TPM プラグインの作成方法
TPM プラグインの作り方は、以下のページを参考にしました。
tmux-#{プラグイン名}.tmux というシェルスクリプトを作っておくと、プラグイン読み込み時にこのファイルを読み込んでくれるので、この中に tmux の設定を書き換える処理を書いておきます。
今回の場合ですと、window-status-format と window-status-current-format に含まれる #{ssh-host} という文字列を、#(./scripts/ssh-host.sh #{pane_pid}) に書き換えるための処理が書いてあります。
# !/usr/bin/env bash
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
script="#($CURRENT_DIR/scripts/ssh-host.sh #{pane_pid})"
pattern="\#{ssh-host}"
do_interpolation() {
local string="$1"
local interpolated="${string/$pattern/$script}"
echo "$interpolated"
}
update_tmux_window_option() {
local option="$1"
local option_value="$(tmux show-window-option -gv "$option")"
local new_option_value="$(do_interpolation "$option_value")"
tmux set-window-option -g "$option" "$new_option_value"
}
update_tmux_window_option "window-status-format"
update_tmux_window_option "window-status-current-format"
ここで使っている #{pane_pid} は、ペインで開いているコマンドのプロセス ID に置き換えられます。2
たいていの場合はシェルのものになるかと思います。
それを、別に用意したシェルスクリプトに渡している訳ですね。
SSH コマンドの取得方法
scripts/ssh-host.sh の中では、まずは #{pane_pid} からそのペインで実行されている ssh コマンドのコマンドライン引数を取得し、次にその中からホスト名を取り出しています。
ssh コマンドのコマンドライン引数の取得方法は、以下のページを参考にしました。
get_ssh_arguments() {
local parent_pid=$1
local arguments=$({ \pgrep -flaP $parent_pid; \ps -o command -p $parent_pid; } \
| \xargs -I{} echo {} \
| \awk '/ssh/' \
| \head -n 1 \
| \sed -E 's/^[0-9]*[[:blank:]]*ssh //' \
)
echo $arguments
}
まず、ps コマンドで #{pane_pid} のプロセスが実行されたときのコマンドを取得し、pgrep で #{pane_pid} の子プロセスが実行されたときのコマンドを取得しています。3
次に、awk コマンドを使ってパターン /ssh/ を含むもののみに絞り込みを行います。
この際に、同じペインで複数の ssh を実行している場合もあるので、その場合は head コマンドを使って一番初めに見付かったものを採用しています。
最後に、sed コマンドで pgrep コマンドで付与される余計なプロセス番号と、ssh コマンドのコマンド名自体を除去しています。
これで、そのペインで実行されている ssh コマンドのコマンドライン引数が取得できました。
SSH の接続先のホスト名の取得方法
続いて、ssh コマンドのコマンドライン引数の中から、接続先ホスト名を取得します。
man コマンドで見てみると、ssh コマンドのパラメータは、以下のように指定するそうです。
NAME
ssh — OpenSSH SSH client (remote login program)
SYNOPSIS
ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port] [-E log_file] [-e escape_char] [-F configfile]
[-I pkcs11] [-i identity_file] [-J destination] [-L address] [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]
[-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] destination [command]
これによると、指定できるオプションの中で、-46AaCfGgKkMNnqsTtVvXxYy は値をとらず、-BbcDEeFIiJLlmOopQRSWw は値をとることがわかります。
つまり、後者のオプションの次に現れる値は、接続先のホスト名ではないってことですね。
get_ssh_host() {
local ssh_command="$1"
while (($# > 0)); do
case "$1" in
-[BbcDEeFIiJLlmOopQRSWw])
shift
;;
-*)
;;
*)
echo "$1" && break
;;
esac
shift
done
}
コマンドライン引数をひとつずつ while 文で回してチェックして、-BbcDEeFIiJLlmOopQRSWw が現れた場合は、そのオプションとその次の引数を破棄し、それ以外のオプションが現れた場合はオプションだけ破棄してます。
そして、それ以外の値が来たときに、それを接続先のホスト名として採用しています。
やらなかったこと
最後に、検討はしたものの、やらなかったことを書いておきます。
~/.ssh/config を使うと、接続先のホスト名に別名を付けることができます。
別名を付けた場合は別名を表示させたかったので、~/.ssh/config の中までは見てません。
ホスト名の前に <ユーザ名>@ を付けると、ユーザーを指定してログインをすることができます。
ssh コマンド実行時に、あえてユーザー名を指定した場合は、それも一緒に表示させたかったので、これを取り除くような処理は書いていません。