Bash補完
complete
Bashであるコマンドに対して補完を出来るようにしてあげるには
complete -F _comp_func cmd
とcomplete
コマンドを使います。
これでcmd
というコマンドに対する補完を_comp_func
という関数で与えます。
-F
で関数を指定。
この_comp_func
という関数は別で定義しないといけませんが、
この関数は通常ダイレクトに使われてほしくないもの(使っても意味無いもの)
なので_
を最初に付けたりして通常のコマンドを分けて置くのが普通です。
(別に_
は絶対必要なものではないがあった方が良い。)
コマンドは複数一緒に指定することが可能。
complete -F _comp_func cmd1 cmd2 cmd3
また、
complete -o default -F _comp_func cmd
と、オプションでdefualt
を付けると対応補完がない場合に
ファイル/ディレクトリの補完をしてくれるようになります。
補完補助関数
COMPREPLY
補完を決める補助関数内では
基本的には
COMPREPLY
という値に配列を入れるとその中身が
コマンドラインで補完に使われる様になります。
1 2 3 |
|
としておけば
$ cmd <Tab>
aaa bbb ccc
と言った感じにTabを押した時に補完が出ます。
ただ、このままだとcmd
の後に何を書いても常に上の三つが
補完候補として表示されるだけです。
$ cmd a<Tab>
aaa bbb ccc
と、一部を書いてもそれを絞り込んで絞ったものだけに補完してくれることはありません。 (まだ3つとも補完の候補として残っているため。)
compgen
途中までの入力を使って候補を絞るためには
COMPREPLY=($(compgen -W "aaa bbb ccc" -- "${cur}"))
と、compgen
コマンドを使います。
-W
で指定した文字列の中から、そのあとで指定された文字から始まるものだけを
ピックアップしてくれます。
(現在入力中のものが-
で始まるオプションみたいのだったりする可能性があるので、
それを唯の文字列として扱うために--
をはさんでおくと安全です。)
$ compgen -W "aaa abc bbb ccc" a
aaa
abc
の様に絞り込めることが確認できます。
つまり上の配列を作る所で、cur
に現在入力中の文字を入れれば
この結果をCOMPREPLY
に入れることで途中までの入力を効かせる事が出来ることになります。
これを持ってくるために次のCOMP_WORDS
等を使います。
COMP_WORDS, COMP_CWORD
補完に使う関数の中では
COMP_WORDS
とCOMP_CWORD
という変数が用意されていて、
それぞれ
COMP_WORDS
: コマンド自身が0番目、それ以降の引数が1番目以降に入った配列。 空白を空けてTabを押した場合には最後に空白が入ります。 従ってコマンド自身は必ずあり、空白おいて次へ行かないとこの補完自体が動かないので、COMP_WORDS
は必ず長さが2以上になります。COMP_CWORD
: 現在カーソルが居る位置。
1 2 3 4 |
|
みたいな物をsource
してみると
$ test_cmd <Tab>
1 test_cmd
$ test_cmd aaa<Tab>
1 aaa test_cmd
$ test_cmd aaa <Tab>
2 aaa test_cmd
$ test_cmd aaa bbb <Tab>
3 aaa bbb test_cmd
$ test_cmd aaa <Tab> bbb
2 aaa bbb test_cmd
みたいな事を確認出来ます(<Tab>
の位置にカーソルがあってそこでTabを押したとします)。
CMOP_WORDS
にはカーソルの位置に依らずコマンドラインに書いた全ての
引数が入っています。
一方、カーソルを戻せばCOMP_CWORD
の数字が変わっているのが分かります。
これらの値は通常
local cur=${COMP_WORDS[COMP_CWORD]}
local prev=${COMP_WORDS[COMP_CWORD-1]}
という値を使うのが定石。
上に書いたcompgen
で使う様に現在の位置の引数をcur
へ入れ、
一つ前の引数をprev
へ入れます。
(prev
は一番最初はコマンド自身になります。)
prev
に関してはcmd -f <file>
みたいなオプションを取る時に、
-f
がprev
に入っていたら補完はファイル名にする、みたいな使い方が出来ます。
ここで、1つ注意としてコマンドライン上で使う関数なので、
関数内で使う変数は特別な理由がない限りは全てlocal
なものにし、
作業に影響が無いようにすることを忘れないように。
(for文を回す時に使うfor i in 1..10
のi
等は先にlocal i
と宣言しておいたり
きちんと処理する必要があります。)
Bash-Completion
Bash-Completion: http://bash-completion.alioth.debian.org/
はBashの補完を拡張するための便利設定が入ったパッケージです。
様々な追加の補完や、補完に関する補助関数なども使える様になります。
公式ページに有るGitのレポジトリからcloneするなり コピーするなりしてきて指示通りにファイルを配置するか、 MacならHomebrewで
$ brew install bash-completions
で/usr/local/etc/bashcompletionというファイルと /usr/local/etc/bash_completion.d/という設定ファイルが 入ったディレクトリがインストールされます。
インストールしたbash_completionの中には
_get_comp_words_by_ref
という関数があり、これに
_get_comp_words_by_ref cur prev
とすると上でCOMP_WORDS
からセットしてるのと同じ様な事が出来ます。
(ただ、ちょっと癖があって上手くいかないことがあるのでCOMP_WORDSを
直接使ったほうが安全。)
他にもいくつか補助関数があるので、 複雑なことをしようとする時にはこれらの補助関数が役にたつかもしれません。
Bash-Completionを有効にするためには.bashrcなんかで
1
|
|
とbash_completionを読み込むようにしておけばOK。 bash_completionを読み込むと、この中でbash_completion.d にあるファイルも全て(.bakや.swpファイル等以外)読み込んでくれます。
Homebrewで入れる場合には
1 2 3 4 |
|
みたいにしておくとHomebrewの設定を変更しても大丈夫になります。
MacのHomebrewでBash補完のファイルをインストールするFormulaを作る
自分で作ったコマンドをHomebrewで管理していて その中にBash補完を入れたい場合や、 また、既にあるコマンドに対してでも自分で補完関数を作って それをパッケージとして管理したい場合についてです。
一番分かりやすいやり方としては、 Bash-Completionをインストールした上で bash_completion.dの中に自分の補助関数を書いたファイルを入れることです。
まず、パッケージ内にetc/bash_completion.d/my_comp_file というファイルを作って、 これをインストールするためにFormula内に
1 2 3 4 5 6 |
|
みたいにインストールを書きます。
もしインストールするものが他にもあるならinstall
関数の中に一緒に書いてください。
インストールする際に名前を変更したいときは、
bash_completion.install "etc/bash_completion.d/my_comp_file" => "different_name"
みたいにするとdifferent_nameというファイル名でインストールされます。
option
でwithout-completions
というものを使ってますが、
補完が必要ない時に--without-completions
とすると補完のファイルはインストールしないで済むようにしています。
これも大体補完ファイルを持ってるパッケージではやってるので
入れておいた方が良いかな、と。
実際のインストールは
bash_completion.install "etc/bash_completion.d/my_comp_file"
ですが、bash_completion
はprefix
+”etc/bash_completion.d”になります。
(prefix
は通常は/usr/local/。)
install
関数の中では、このパスの変数.install
の形に
与えた物をそのパスの中にインストールすることが出来ます。
つまり、上の場合はmy_comp_fileを/usr/local/etc/bash_completion.d の中にインストールすることになります。
元のファイルは別に何処にあっても良いので、 comp/my_comp_fileとかに起きたければ
bash_completion.install "comp/my_comp_file"
としてあげれば良いだけです。(上の例ではインストール先と同じになるようにしています。)
これでbash_completionを読み込む設定をしてあれば このmy_comp_fileも同時に読み込まれる様になります。
ここで、Furmulaのinstall
関数内でのちょっと引っかかった部分として、
この関数内では同じファイルを二度使おうとするとエラーが出ます。
つまり、
bash_completion.install "etc/bash_completion.d/my_comp_file"
etc.install "etc/bash_completion.d/my_comp_file"
みたいに同じファイルを別々の場所にインストールしようとする事は出来ません。
ちょっと詰まったのが、etcの中に既に他にインストールしたいものが入っていて、
1 2 3 4 5 6 |
|
の様に、まずetcディレクトリをインストールしてしまう様に
してあってそこにさらにbash_completion
へのインストールを書いた時。
ディレクトリごと指定したものでも、その中身が全て使われる事になるので、
この場合もbash_completion.install
の所で
Error: No such file or directory - etc/bash_completion.d/my_comp_file
というエラーが出ます。
なので、最初のetcのインストールでは
prefix.install
でetc全体を指定せずに
`etc.install` "etc/file"
みたいに個別に指定する必要があります。
この辺ちょっと注意。
また、余り関係ないですが、この様にetcをインストールしたい場合、
prefix.install
でetcディレクトリごとやると、
/usr/local/etc/内にそれぞれのファイルがリンクとしてインストールされるのですが
(ディレクトリがある場合はそのディレクトリを掘った上で中に
ファイルのリンクが貼られる)、
etc.install
はちょっと特殊でこれでインストールした場合は
実態がコピーされます。
このコピーはパッケージをuninstallしても消えません。
example-formula.rb: https://github.com/Homebrew/homebrew/blob/master/Library/Contributions/example-formula.rb
を見るとetc
に関しては
# Configuration stuff that will survive formula updates
と書いてあって、etcに入れるような設定ファイルはアップデート時にも残す 的な事をしたいことから一度インストールしたらインストールしっぱなし、 な状態にする様です。
こうなると後は手動で消す以外にない状態になります。
読み込まなければ良いだけなので悪さをすることはありませんが、 アンインストール時にも消したいと思ったら、
(prefix+'etc').install "etc/file"
の様に、etc
を直接使わずprefix+'etc'
を自分で作ってそれを
使ってinstall
するようにすると、
/usr/local/etcにシンボリックリンクとしてインストール出来、
パッケージアンインストール時にも消せる様になります。
その辺最近 Brew-file でも色々アップデートしたのでBrew-fileのFormulaや補完関数も参考になるかも。