bashでgit comまでタイプしてタブキーを打つとcommitと補完される謎を追う
ひっそりぃはUbuntu Linux 13.04を使っているのですが、git commitしようとして、何気なくgit comまでタイプしCtrl-i(タブキー)を押すと、なんとcommitと補完され驚きました。
シェルはbashを使っているので、コマンド名やディレクトリ名が補完されるのは当然なのですが、コマンドの引数まで補完されるとは。
bashにこんな機能あったっけ?
と気になったので調べてみた。
調査にはプロセスのsystem callやsignalをtraceできるstraceコマンドを使います。全てのtraceを追うと大変そうなので、補完のためのデータベースを読みに行くのでは?とあたりをつけ、open()のみ追うことにします。
コマンドは、以下の様になります。
bashが起動する過程でのopen()結果がずらずらと表示され、以下の状態で止まりました。
ここで、キーボードにて「git com」までタイプし、おもむろにタブキーをたたくと、
シェルはbashを使っているので、コマンド名やディレクトリ名が補完されるのは当然なのですが、コマンドの引数まで補完されるとは。
bashにこんな機能あったっけ?
と気になったので調べてみた。
調査にはプロセスのsystem callやsignalをtraceできるstraceコマンドを使います。全てのtraceを追うと大変そうなので、補完のためのデータベースを読みに行くのでは?とあたりをつけ、open()のみ追うことにします。
コマンドは、以下の様になります。
$ strace -e open bash
bashが起動する過程でのopen()結果がずらずらと表示され、以下の状態で止まりました。
: (省略)
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/etc/bash_completion.d/zeitgeist-daemon", O_RDONLY|O_LARGEFILE) = 3
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/home/hissorii/.bash_history", O_RDONLY|O_LARGEFILE) = 3
open("/home/hissorii/.bash_history", O_RDONLY|O_LARGEFILE) = 3
open("/lib/terminfo/s/screen", O_RDONLY|O_LARGEFILE) = 3
open("/etc/inputrc", O_RDONLY|O_LARGEFILE) = 3
hissorii@ubuntu:~$
ここで、キーボードにて「git com」までタイプし、おもむろにタブキーをたたくと、
と結果が返ってきます。hissorii@ubuntu:~$ git comopen("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/usr/share/bash-completion/completions/git", O_RDONLY|O_LARGEFILE) = 3
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
--- SIGCHLD (Child exited) @ 0 (0) ---
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
--- SIGCHLD (Child exited) @ 0 (0) ---
--- SIGCHLD (Child exited) @ 0 (0) ---
--- SIGCHLD (Child exited) @ 0 (0) ---
--- SIGCHLD (Child exited) @ 0 (0) ---
mit
一行目は「git com」のタイプの直後にopen() traceが被っていますが、気になるものが2行目に見えています。
なにやら /usr/share/bash-completion/completions/git というファイルをオープンしています。
ここでstraceを終了し、このファイルが何者なのか調べてみます。
ふむ、バイナリファイルではなさそうなので中身を見てみると、シェルスクリプトでした。hissorii@ubuntu:~$ file /usr/share/bash-completion/completions/git
/usr/share/bash-completion/completions/git: UTF-8 Unicode text
hissorii@ubuntu:~$
さらに近辺のファイルを眺めてみると、
/usr/share/bash-completion/bash_completion が親玉のシェルスクリプトですね。hissorii@ubuntu:~$ ls -F /usr/share/bash-completion/
bash_completion completions/ helpers/
hissorii@ubuntu:~$
改めてbashを起動したときのstrace結果を見てみると、確かにこのスクリプトをopen()していました。
open("/usr/share/bash-completion/bash_completion", O_RDONLY|O_LARGEFILE) = 3
次にcompletionsディレクトリですが、量が多いのでgで始まるものの一部だけを見てみるとこんな感じ。
これらが各コマンドの引数補完をするスクリプト群ですね。
hissorii@ubuntu:~$ ls /usr/share/bash-completion/completions/ | grep ^g | head -15
g++
g4
g77
gcc
gcj
gcl
gdb
gdbus
genaliases
gendiff
genisoimage
getent
git
gitk
gkrellm
hissorii@ubuntu:~$
bash_completionスクリプトとgitスクリプトがそれぞれどのパッケージに属するかを見てみると、
となるので、bash-completionパッケージがbashでのコマンド引数の補完を行う大元のパッケージですね。hissorii@ubuntu:~$ dpkg -S /usr/share/bash-completion/bash_completion
bash-completion: /usr/share/bash-completion/bash_completion
hissorii@ubuntu:~$ dpkg -S /usr/share/bash-completion/completions/git
git: /usr/share/bash-completion/completions/git
hissorii@ubuntu:~$
各コマンド、例えばgitパッケージの作成者がcompletionsディレクトリに置くスクリプトを提供することでgitコマンドの引数補完ができるようになる訳です。
逆に言うと、completionsディレクトリにスクリプトを提供していないコマンドについては引数補完ができない、ということになります。
改めて、bash-completionというパッケージがbashでのコマンド引数補完の犯人?ということが分かったので、ちょっとググってみました。
以下がオフィシャルサイトのようです。
http://bash-completion.alioth.debian.org/
単なるコマンドの引数補完だけではなく、たとえばsshで接続先ホスト名の補完などもできるようです。
/etc/hostsや~/.ssh/known_hostsを参照したりするのかな?
Copyrightを見ると2008年には既に存在していたようです。全く知りませんでした。
知らず知らずのうちに使っていたかもしれません。
感謝感謝。
Comment
コメントの投稿
Trackback
http://hissorii.blog45.fc2.com/tb.php/231-3376c15e