コンマで区切られたパラメータリストの完成ルールを作成したいと思います。たとえば、サーバー名の一覧を受け取るコマンドがあります。
myscript -s name1,name2,name3
この時点で、私は次の完成したコンテンツを正常に作成しました。
_myscript () {
local cur prev opts
_get_comp_words_by_ref cur prev
opts='-s'
servers='name1 name2 name3'
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
else
case "${prev}" in
-s)
if [[ "$cur" == *,* ]]; then
local realcur prefix
realcur=${cur##*,}
prefix=${cur%,*}
COMPREPLY=( $(compgen -W "${servers}" -P "${prefix}," -- ${realcur}) )
else
COMPREPLY=( $(compgen -W "${servers}" -- ${cur}) )
fi
;;
*)
# do nothing
;;
esac
fi
}
しかし、ここには少なくとも2つの問題があります。
- 現在の値の提案には、プレフィックスに以前の値がすべて含まれます。
- 重複した値は考慮されません。
このような状況のベストプラクティスは何ですか?たぶんbash-completionsにcsvリストのバンドル機能はありますか?
答え1
COMPREPLY
bashはディスプレイで直接値を使用してからユーザーのテキストを置き換えるため、基本的に説明する問題を解決する方法はありません。一方、必要なものを取得するには、最初に可能な補完を作成する必要があります(追加のサーバー名のみがあり、プレフィックスはありません)。 bashを表示するには、bashがユーザーテキストを競合しない最も長い文字列に置き換えようとしたときにスクリプトを再度呼び出してプレフィックス付きのテキストを生成する必要があります。bashにはその機能はありません。
私が考えることができる最善の方法は、プレフィックス全体()を持つ最初の単語のみをCOMPREPLY
使用して生成し、可能な限り完成したCOMPREPLY=( "${prefix},"$(compgen -W "${servers[@]}" -- ${realcur}) )
ものが1つだけ自動的に正しく完了するのに対し、可能な補完が複数ある場合、bashは削除しないことです。 (最初の単語にはCOMPREPLY
プレフィックス全体があるため、現在入力されているテキストと一致し、ユーザーのテキストを置き換えるためにbashで選択されます。)プレフィックスなしのオプションが表示されます。 - すでに入力したオプションは除外されます。接頭辞付きの単語が含まれているため、出力は次のようになります。
$ command -s banana,a
ananas apricot banana,apple
「apple」は「b」で始まる接頭辞があるため、完成オプションの最後に来ます。とても混乱しています。だから私はこれをお勧めしません。
重複項目に関して - 重複項目を表示したくない場合は、$prefix
その部分を分解して(単純に:)、まだリストされていない名前IFS="," prefix_parts=($prefix)
だけを保持しながら繰り返してください。$servers
入力するのは面倒なのでここには表示しません。しかし、比較的マイナーなので、管理できると確信しています :-)。
全体的に、少なくともbashにこれを行うには、入力オプションにコンマ区切りの値を使用する必要はないと思います。
次のオプション形式をサポートできます。command -s <server> [<server> [..]]
次に、そのオプションのすぐ次の項目以外の項目を完成させるには、オプション(一致する文字列を含む)が見つかるまで配列を-s
スキャンし、オプションが「-s」の場合は次のようにします。サーバー名を完成させる必要があります。$COMP_WORDS
$COMP_CWORD
-*
答え2
リストされているすべての問題を解決する方法がありますが、:
区切り文字として使用する必要があります。これは、コロンがreadlineなどで特別に処理されるためです。したがって、あなたの例には次の構文があります。
myscript -s name1:name2:name3
名前にコロンが含まれている場合、この方法は機能しません。
例の完成したスクリプトは次のとおりです。
_myscript () {
local cur prev words cword
_init_completion -n : || return
local opts servers
opts='-s'
servers=(
name1
name2
name3
)
# also assume first argument will be an option
if [[ ${cur} == -* || $cword -eq 1 ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
else
case "${prev}" in
-s)
if [[ "$cur" == *:* ]]; then
local realcur prefix chosen remaining
realcur="${cur##*:}"
prefix="${cur%:*}"
chosen=()
IFS=$':\n' read -ra chosen <<< "$prefix"
remaining=()
readarray -t remaining <<< "$(printf '%s\n' "${servers[@]}" "${chosen[@]}" | sort | uniq -u)"
if [[ ${#remaining[@]} -gt 0 ]]; then
COMPREPLY=( $(compgen -W "${remaining[*]}" -- "$realcur") )
# add separator if user tabs again after entering a complete name
if [[ ${#COMPREPLY[@]} -eq 1 && ${#remaining[@]} -gt 0 && "$realcur" == "${COMPREPLY[0]}" ]]; then
COMPREPLY=("${COMPREPLY[0]}:")
fi
if [[ ${#remaining[@]} -gt 1 ]]; then
compopt -o nospace
fi
fi
else
COMPREPLY=( $(compgen -W "${servers[*]}" -- "$cur") )
# add separator if user tabs again after entering a complete name
if [[ ${#COMPREPLY[@]} -eq 1 && "$cur" == "${COMPREPLY[0]}" ]]; then
COMPREPLY=("${COMPREPLY[0]}:")
fi
compopt -o nospace
fi
;;
*)
# do nothing
;;
esac
fi
} &&
complete -F _myscript myscript