タブ補完の仕組み
オートコンプリートはメッセージングアプリやIDEだけでなく、Unix系システムのシェルにも古くから存在します。bash や zsh には強力な補完エコシステムがあり、コマンド名やファイル名だけでなく、コマンド引数(オプションやサブコマンド)に対する補完も可能です。
- コマンド補完: PATH を参照して入力した文字列で始まるコマンド名を候補として表示します。
- ファイル名補完: 相対・絶対パスを補完し、候補を絞り込みます。
- 引数補完: コマンド名の後に来る引数(例: サブコマンドやフラグ)を補完します。これが最も柔軟で、ユーザー体験に大きく寄与します。
自作補完を追加する理由
- サブコマンドやオプションをユーザーに見つけてもらいやすくする。
- タイピング量を減らし、誤入力を防ぐ。
- 共有スクリプトや CLI ツールの使いやすさを向上させる。
重要: 補完はあくまで補助です。スクリプト本体で正しい引数検証やヘルプ表示を行ってください。
例: 単純なコマンド
まずは例として、簡単な todo 管理用スクリプトを用意します。ここでは関連部分のみ抜粋します。
#!/usr/bin/env zsh
FILE="$HOME/.local/state/todos/data"
if [ "$#" -eq "1" ] && [ "$1" = "edit" ] ; then
"${EDITOR:-vi}" "$FILE"
elif [ "$#" -eq "1" ] && [ "$1" = "help" ] ; then
echo "Usage: $(basename $0) [ edit | help ]"
else
<"$FILE" grep -v ^~
fi
このスクリプトを todos という名前で保存し実行権限を付け、PATH に置くと次のような振る舞いになります。
- todos: データファイルの中身を表示(先頭が ~ の行は除外)
- todos help: 使い方を表示
- todos edit: データファイルをエディタで開く
補完はスクリプトの動作に依存しません。空のスクリプトでも実行可能かつ PATH 上にあれば補完は機能します。
zsh 用のシンプルな補完スクリプト
zsh の場合は compadd を使ったハンドラを定義し、compdef でコマンドに紐付けます。最小限の例は次の通りです。
_complete_todos() {
compadd help edit
}
autoload -Uz compinit
compinit
compdef _complete_todos todos
- _complete_todos は補完ハンドラです。
- compadd に渡した単語が補完候補になります。
- compinit を読み込み compdef で todos に紐付けます。
このコードは ~/.zshrc に追記するか、fpath に置くファイル(名前は先頭にアンダースコアを付ける慣例)として保存しても良いです。開発中は対話的に直接実行して挙動を確認できます。
Bash の違い
Bash の補完は zsh より低レベルです。Bash 側は補完候補の選別ロジックを自前で行うことを期待します。単純なハンドラの例:
_complete_todos_bash() {
COMPREPLY=(help edit)
}
complete -F _complete_todos_bash todos
ここで COMPREPLY は補完候補を格納する配列です。このままでは “he” の入力でもすべての候補が表示されてしまいます:
$ todos he
help edit
Bash では COMP_WORDS(コマンドラインを単語配列にしたもの)と COMP_CWORD(現在の補完対象単語のインデックス)を使って、現在補完中の単語を特定し、適切にフィルタする必要があります。便利なコマンドに compgen があり、-W で静的候補リストから部分一致を生成できます:
$ compgen -W "one two three" o
one
$ compgen -W "one two three" t
two
three
この仕組みを使うと次のようにコンテキストに応じた補完が可能です。
_complete_todos_bash() {
COMPREPLY=( $( compgen -W "edit help" -- "${COMP_WORDS[$COMP_CWORD]}" ) )
}
complete -F _complete_todos_bash todos
zsh と Bash 両対応の汎用スクリプト
実際に配布可能なスクリプトは、実行しているシェルを判定して適切なコードを有効化すると便利です。簡単な例を示します。
SUBCOMMANDS=(help edit halt)
_complete_todos_zsh() {
compadd $SUBCOMMANDS
}
_complete_todos_bash() {
COMPREPLY=( $( compgen -W "${SUBCOMMANDS[*]}" -- "${COMP_WORDS[$COMP_CWORD]}" ) )
}
if [ -n "${ZSH_VERSION:-}" ]; then
autoload -Uz compinit
compinit
compdef _complete_todos_zsh todos
elif [ -n "${BASH_VERSION:-}" ]; then
complete -F _complete_todos_bash todos
fi
ポイント:
- ZSH_VERSION / BASH_VERSION を見て補完の接続方法を切り替える。
- SUBCOMMANDS を共通変数にすることで双方のハンドラで同じ候補を使う。
- Bash は配列→文字列の変換などで若干の工夫が必要。
ファイルに保存して ~/.bashrc や ~/.zshrc からソースすれば利用できます。
. /path/to/completion.sh
次のステップと応用例
ここからは実用的な拡張や配布・テストに役立つパターンを紹介します。
動的候補の生成
サブコマンドが固定でない場合、外部ソースから候補を生成できます。例えばプロジェクトディレクトリ内のファイル一覧、あるディレクトリにあるブランチ一覧、あるいは JSON ファイルに格納したメタ情報などを読み取って候補にします。
zsh の例:
_complete_dynamic() {
local items
items=( $(<"$HOME/.local/state/todos/list") )
compadd -- $items
}
Bash の例:
_complete_dynamic_bash() {
local cur
cur="${COMP_WORDS[$COMP_CWORD]}"
local items
IFS=$'\n' read -d '' -r -a items < "$HOME/.local/state/todos/list" || true
COMPREPLY=( $( compgen -W "${items[*]}" -- "$cur" ) )
}
注意: 外部コマンドやファイルを参照する際はパフォーマンスを意識してください。補完は対話操作なので遅延がUXを損ねます。
オプション/フラグの補完
–help や –verbose のような長いフラグを補完する場合、候補リストにそれらを含めれば良いだけですが、フラグの値(例: –output=FILE)や値が取れるフラグ(例: –branch BRANCH)ではプロンプト位置を見て候補を生成する必要があります。
- zsh: compadd の引数や _arguments ユーティリティを使う
- bash: COMP_WORDS, COMP_CWORD を参照して位置に応じた候補を返す
パスやコマンド候補を混ぜる
compgen には -f(ファイル)や -d(ディレクトリ)、-c(コマンド)といったオプションがあります。これらを組み合わせると便利です。
例: 現在の単語がファイルパスの候補なら -f を使う、そうでなければサブコマンド候補を出す、といった分岐が可能です。
配布とインストール
推奨: 補完スクリプトは個別ファイルにして、ドキュメントにインストール手順を明示してください。一般的な方法:
- zsh: ファイル名を先頭にアンダースコアを付け、$fpath のいずれかに置く。ユーザー向けには ~/.zsh/completions/ に置き、~/.zshrc で fpath に追加する方法を案内する。
- bash: /etc/bash_completion.d/ に配置(システム全体)するか、ユーザーは ~/.bash_completion.d/ を使う。~/.bashrc から source する方法も案内する。
パッケージ化: Homebrew、apt などのパッケージに含める場合は、インストール先とシェルの再読み込み/補完キャッシュの更新手順を README に記載してください。
テストと受け入れ基準
自動テストは難しいですが、少なくとも次を確認しておくと良いでしょう:
- 補完対象コマンドを PATH に置いた状態で候補が出る。
- 部分入力に対して適切にフィルタされる(例: “he” -> “help” のみ)。
- 動的候補が最新情報を反映している(キャッシュがある場合は更新方法を明示)。
- パフォーマンスが許容範囲(Tab での応答が目立って遅くない)。
受け入れ基準の例:
- 単体: todos の補完で help と edit が表示されること
- コンテキスト: todos he
で help のみが補完されること - 互換性: zsh と bash の両方で主要機能が動作すること
よくある失敗と対処
- 補完が有効にならない: ~/.zshrc や ~/.bashrc でソースしているか、ファイルが実行可能か、fpath に追加されているか確認する。
- 遅い補完: 外部コマンドを呼び過ぎていないか確認。必要ならキャッシュを導入する。
- 文字エンコーディングやロケール問題: 候補に日本語が含まれる場合、ロケール設定が原因で挙動が変わることがある。
- Bash で候補がフィルタされない: COMP_WORDS と COMP_CWORD の使い方を見直す。
代替手段とツール
- Python ベースの argcomplete(Python CLI 用): argparse 等と連携して補完スクリプトを生成できる。
- clap(Rust)や cobra(Go): これらの CLI フレームワークは補完スクリプトを自動出力する機能がある。
- テキストベースの説明: 補完を提供しない代わりに充実した –help を用意するという選択肢もある。
実務的な判断メモリーモデル
- 小さな個人スクリプト: 手早く zsh の compadd を ~/.zshrc に入れる。
- 共有ライブラリやチームツール: 両対応のスクリプトを用意し README にインストール手順を明記する。
- 大規模 CLI: フレームワーク側で補完生成をサポートしているならそれを使う。
ロール別チェックリスト
- 開発者:
- 補完スクリプトをリポジトリに含める
- インストール手順を README に追加
- 動作確認を少なくとも zsh と bash で行う
- メンテナ:
- SUBCOMMANDS を動的に生成する場合のパフォーマンスを監視
- 破壊的変更があれば補完の更新手順を通知
- システム管理者:
- システム全体に配布する場合、/etc/bash_completion.d/ や /usr/share/zsh/site-functions/ の配置ルールを守る
スニペット集(チートシート)
zsh: 固定候補
_complete_example() {
compadd start stop restart
}
zsh: _arguments を使った例(簡易)
_arguments \
'1: :((start stop restart))' \
'--verbose[詳細出力を有効にする]'
bash: 部分一致を使う基本形
_complete_example_bash() {
local cur
cur="${COMP_WORDS[$COMP_CWORD]}"
COMPREPLY=( $(compgen -W "start stop restart" -- "$cur") )
}
complete -F _complete_example_bash example
互換性と移行の注意点
- zsh の compadd や _arguments は非常に便利だが、Bash には存在しない。両対応が必要なら抽象レイヤーを自分で作る。
- 古い Bash(特に POSIX 準拠に近い環境)では配列操作が限られるため、配列→文字列→配列の変換に注意する。
- Windows の WSL や Cygwin でも基本的に同じだが、PATH 文字コードやシェルの実装差で微妙に動作が異なることがある。
セキュリティとプライバシーの注意
- 補完ハンドラ内で外部から得た不確かなデータ(例えばネットワーク経由で取得した一覧)を安易に eval したり表示に使ったりしない。
- ローカルファイルの一覧を補完に使う場合でも、権限が制限された環境を想定してエラー処理を行う。
短い告知文(100–200 文字)
新しい todos CLI 用のタブ補完を追加しました。zsh と bash 両対応で、help/edit/halt の補完を提供します。インストールは completion.sh を ~/.bashrc または ~/.zshrc からソースするだけ。詳細と配布方法は README を参照してください。
SNS 用プレビュー候補
- OG タイトル: Bash と zsh で自作タブ補完を作る方法
- OG 説明: 自作 CLI に zsh / bash 両対応のタブ補完を追加する手順と配布・テストのチェックリストを解説します。
まとめ
- タブ補完はユーザー体験を劇的に向上させる小さな投資です。
- zsh は高水準なユーティリティ(compadd, _arguments)を提供し、Bash は低レベルだが compgen による柔軟性がある。
- 両方に対応するには実行中のシェルを判定して適切なハンドラを登録するのが現実的なアプローチです。
重要: 補完は補助機能です。本体の入力検証やヘルプ表示は必ず実装してください。