LoginSignup
17
20

More than 5 years have passed since last update.

bashに自作のホスト名補完機能を追加してsshを楽にする

Last updated at Posted at 2016-01-14

はじめに

bash-completionをインストールすることで、sshやpingなどのコマンドで、タブキーによるホスト名補完が可能になります。
これを『自作のホスト名補完に 置き換える』例は様々ありますが、『自作のホスト名補完を 追加する 』手順の例がなかなか見つからないので、私の実装例をTipsとしてまとめました。

以下は、bash-completionのversion 1.3を検証環境として記載しています。
また、私自身bash-completion設定を初めて書いたので、お作法を無視しているかもしれません。ご承知ください。
(2016/01/19追記)
本手順に従うと、補完機能はシステム共通設定として設定されます。個人設定を作成する際には別の手順を参照してください。

bash-completion導入方法

以下が参考になります。
ぶっちゃけ、yum install bash-completionのようなことをすればOKです。

自作ホスト名補完機能の実装例

自作ホスト名補完機能の実装については、Qiita内に幾つか良記事があります。

これらの実装例の欠点については、以下で述べていきます。

ホスト名補完はどう実現しているのか

実装例を説明する前に、bash-completionのホスト名補完機能を解読します。

ホスト名一覧を返す関数

Linux系では/etc/bash_completionファイルにて、
Macのbrewインストールでは/usr/local/etc/bash_completionファイルにて、
以下の2つの関数が実装されています。
※ 本文以下では、Macのパスについては省略します。/usr/localのプレフィクスが必要と考えてください。

  • _known_hosts()
  • _known_hosts_real()

前者はdeprecatedな関数であり_known_hosts_real()を呼び出しているだけです。後者の_known_hosts_real()がホスト名補完の実装となっています。

この_known_hosts_real()関数の中では以下のファイルやコマンド(詳細は割愛)を参照して「known_hosts」を抽出しています。

  • ssh_configファイル
  • ssh_known_hostsファイル
  • avihi-browseコマンド

このように生成されたknown_hostsは、ホスト名やIPアドレスを引数に必要とするコマンドに渡されて利用されます。

known_hostsの利用
complete -F _known_hosts traceroute traceroute6 tracepath tracepath6 ping \
    ping6 fping fping6 telnet host nslookup rsh rlogin ftp dig mtr \
    ssh-installkeys showmount

したがって、 _known_hosts_real()を拡張すればホスト名補完に自作機能を追加できる ということになります。

自作拡張機能の実装の問題

さて、問題となるのは、_known_hosts_real()の実装場所です。

上記したとおり、この関数は/etc/bash_completionファイルに存在しています。
このファイルは、 bash_completionの親ファイル です。
つまり、yumやbrewで入れた場合、管理対象になっているファイルなわけです。

/etc/bash_completionを改造してしまうと、アップデートが不便になってしまいます。

ところで、bash_completionでは/etc/bash_completion.d/フォルダに設定ファイルを追加することで、ファイル単位で機能を読みこませることが可能です。
機能を拡張するなら、このフォルダに設定ファイルを配置する方が望ましいですね。

ではどうするか。

ホスト名補完を拡張しよう

長々と書いた前節の通り、bash-completionのホスト名補完を拡張するためには、_known_hosts_real()関数を拡張する必要があります。とはいえ、/etc/bash_completionファイルに変更を加えたくはありません。

まとめると、要件はこうです。

  • /etc/bash_completionファイルに変更を加えない
  • /etc/bash_completion.d/以下にファイルを配置することでロードする
  • 既存の_known_hosts_real()を潰さない

実装例

コード

何はともあれコードから紹介します。
例としてファイルの名前はmy_known_hostsとして、以下のコードを/etc/bash_completion.d/my_known_hostsに配置します。
自作コマンド部分は任意のコマンド(後述)に置き換えましょう。

/etc/bash_completion.d/ex_known_hosts
have 自作コマンド &&
{

_save_function() {
    local ORIG_FUNC=$(declare -f $1)
    local NEWNAME_FUNC="$2${ORIG_FUNC#$1}"
    eval "$NEWNAME_FUNC"
}

_save_function _known_hosts_real _org_known_hosts_real
_known_hosts_real()
{
    _org_known_hosts_real "$@"

    local __cachedir __cachefile __expired
    __cachedir=${HOME}
    __cachefile=.knownhosts.cache
    __expired=$(find ${__cachedir} -name ${__cachefile} -maxdepth 1 -mmin -1 2>/dev/null)
    if [ ${#__expired} -le 0 ]; then
        COMPREPLY=( "${COMPREPLY[@]}" $( \
            compgen -W "$(自作コマンド 2>/dev/null | tee ${__cachedir}/${__cachefile} 2>/dev/null)" \
                ${COMP_WORDS[COMP_CWORD]} \
        ))
    else
        COMPREPLY=( "${COMPREPLY[@]}" $( \
            compgen -W "$(cat ${__cachedir}/${__cachefile} 2>/dev/null)" \
                ${COMP_WORDS[COMP_CWORD]} \
        ))
    fi
    return 0
}

}

解説(1): 自作コマンド

コード中の自作コマンドは、任意のスクリプトを実行することを想定しています。
この自作コマンドこそが、ホスト名補完機能の核です。

この自作コマンドは「ホスト名orIPアドレス一覧を出力すること」が必須要件です。
例えばこんな感じです。(役に立たない例だ・・・)

自作コマンド
#!/bin/bash -
grep '^[0-9]' /etc/hosts | awk '{print $2}'

この自作コマンドはこんな出力を得ます。

$ ./自作コマンド
localhost
host_A
host_B

出力する内容はconsulのメンバー一覧でも、DNSのホスト名一覧でも、ZabbixやMackerelのホスト名一覧でもいいでしょう。

解説(2): _save_function()

これが_known_hosts_real()に機能拡張するためのトリックです。
この関数は、既存の関数をリネームする事ができます。
なお、この実装はmivok.netのBash function renaming and overridingを真似たものです。

_save_function()関数により、以下のコマンドでオリジナルの_known_hosts_real()関数は_org_known_hosts_real()関数にリネームされます。

_save_function利用例
_save_function _known_hosts_real _org_known_hosts_real

リネームしてあれば、自作の_known_hosts_real()の中から明示的に呼び出すことができます。
元の関数の実装を汚すことなく機能拡張できる ようになるわけです。

_known_hosts_real()関数
_known_hosts_real()
{
    _org_known_hosts_real "$@"

    好きな実装をする
}

なお、上記の_org_known_hosts_real "$@"の実行結果は、COMPREPLY配列に格納されています。
そのため、後半のCOMPREPLY=部分では、COMPREPLY配列を上書きしないように、データを追加するようなコードとしています。

解説(3): キャッシュ関連

if〜elseの直前で色々変なことをしている部分がありますが、それはキャッシュ関連の処理です。
この実装例は 自作コマンドの応答が遅いことを想定 しており、bash_completion内でキャッシュすることで高速化を図っています。

キャッシュの破棄はこのfind文で判定しています。

キャッシュ破棄の判定
__expired=$(find ${__cachedir} -name ${__cachefile} -maxdepth 1 -mmin -1 2>/dev/null)

この例ではキャッシュ時間は1分間です。
なんでfindなんだろうと思うかもしれませんが、 LinuxとMacでコマンドオプションが違うため 同じオプションで使えるfindを採用したという経緯です。

if〜elseにおいて、キャッシュを再生成する際はteeと組み合わせた以下の実装へ。

キャッシュ再生成
        COMPREPLY=( "${COMPREPLY[@]}" $( \
            compgen -W "$(自作コマンド 2>/dev/null | tee ${__cachedir}/${__cachefile} 2>/dev/null)" \
                ${COMP_WORDS[COMP_CWORD]} \
        ))

キャッシュを利用する場合はcat文の実装へ進みます。

キャッシュを利用するとき
        COMPREPLY=( "${COMPREPLY[@]}" $( \
            compgen -W "$(cat ${__cachedir}/${__cachefile} 2>/dev/null)" \
                ${COMP_WORDS[COMP_CWORD]} \
        ))

我ながらDRY的にカッコ悪い実装だなと思いますが・・・

まとめ

オリジナルのbash-completionを汚さずにホスト名補完機能を拡張する実装例を紹介しました。ついでに、キャッシュの実装例も紹介しました。

実装例を利用する場合は、自作コマンド部分を用意・置き換えれば応用が効くことと思います。キャッシュ時間も環境やコマンドに応じて変更してください。

17
20
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
20