はじめに
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アドレスを引数に必要とするコマンドに渡されて利用されます。
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
に配置します。
※ 自作コマンド
部分は任意のコマンド(後述)に置き換えましょう。
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 _known_hosts_real _org_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を汚さずにホスト名補完機能を拡張する実装例を紹介しました。ついでに、キャッシュの実装例も紹介しました。
実装例を利用する場合は、自作コマンド
部分を用意・置き換えれば応用が効くことと思います。キャッシュ時間も環境やコマンドに応じて変更してください。