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
}

그러나 여기에는 적어도 두 가지 문제가 있습니다.

  1. 현재 값에 대한 제안에는 접두사에 이전 값이 모두 포함됩니다.
  2. 중복된 값은 고려하지 않습니다.

이와 같은 상황에 대한 모범 사례는 무엇입니까? 어쩌면 bash-completions에는 csv 목록에 대한 번들 기능이 있습니까?

답변1

COMPREPLYbash 는 디스플레이에서 직접 값을 사용한 다음 사용자의 텍스트를 대체하기 때문에 기본적으로 설명하는 문제를 해결할 방법이 없습니다 . 반면 원하는 것을 얻으려면 먼저 가능한 완성을 생성해야 합니다(추가 서버 이름만 있고 접두사는 없음). bash를 표시하려면 bash가 사용자 텍스트를 충돌하지 않는 가장 긴 문자열로 바꾸려고 할 때 스크립트를 다시 호출하여 접두사가 있는 텍스트를 생성해야 하며 bash에는 이에 대한 기능이 없습니다.

내가 생각할 수 있는 최선의 방법은 전체 접두사( )가 있는 첫 번째 단어 COMPREPLY만 사용하여 생성하여 COMPREPLY=( "${prefix},"$(compgen -W "${servers[@]}" -- ${realcur}) )가능한 완성이 하나만 있으면 자동으로 올바르게 완료되는 반면, 가능한 완성이 여러 개인 경우 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

관련 정보