Bash完了コンマ区切り値

Bash完了コンマ区切り値

コンマで区切られたパラメータリストの完成ルールを作成したいと思います。たとえば、サーバー名の一覧を受け取るコマンドがあります。

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つの問題があります。

  1. 現在の値の提案には、プレフィックスに以前の値がすべて含まれます。
  2. 重複した値は考慮されません。

このような状況のベストプラクティスは何ですか?たぶんbash-completionsにcsvリストのバンドル機能はありますか?

答え1

COMPREPLYbashはディスプレイで直接値を使用してからユーザーのテキストを置き換えるため、基本的に説明する問題を解決する方法はありません。一方、必要なものを取得するには、最初に可能な補完を作成する必要があります(追加のサーバー名のみがあり、プレフィックスはありません)。 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

関連情報