Bash, Case 문을 사용하여 단어가 배열에 있는지 확인하세요.

Bash, Case 문을 사용하여 단어가 배열에 있는지 확인하세요.

제한된 사전 정의 목록의 단어를 매개변수로 받아들여야 하는 스크립트를 작성 중입니다. 또한 꼭 이루어지길 바랍니다. complete와 사이의 중복을 피하기 위해 목록을 변수에 저장합니다 case. 그래서 저는 이것을 썼고 완성은 작동하지만 case문은 작동하지 않습니다. 왜? 사례문 매개변수를 만들기 위해 변수를 사용할 수는 없나요?

declare -ar choices=('foo' 'bar' 'baz')
function do_work {
  case "$1" in 
    "${choices[*]}")
      echo 'yes!'
      ;;
    *)
      echo 'no!'
  esac
}
complete -W "${choices[*]}" do_work

답변1

의 목록은 구분된 목록 complete -W list으로 해석되며 완료형이 고려됩니다.$IFS$IFS

따라서 다음과 같은 경우:

complete -W 'a b,c d' do_work

do_worka공백이 포함된 경우 와 b,c공백이 포함된 경우와 포함되지 않은 경우에 완성 기능이 제공됩니다 .d$IFSa bc d$IFS,

그래서 대부분 설계상 깨졌습니다. 또한 임의의 문자열을 완성으로 제공하는 것도 허용하지 않습니다.

이러한 제한 사항을 적용할 때 할 수 있는 최선의 방법은 $IFS수정되지 않을 것이라고 가정하고(따라서 공백, 탭 및 줄 바꿈이 항상 포함되므로 완성 단어에 문자를 사용할 수 없음) 다음을 수행하는 것입니다.

choices='foo bar baz'
do_work() {
  case "$1" in
    (*' '*) echo 'no!';;
    (*)
      case " $choices " in 
        (*" $1 "*) echo 'yes!';;
        (*) echo 'no!';;
      esac;;
  esac
}
complete -W "$choices" do_work

readonly IFS수정 되지 않도록 를 추가할 수 있지만 , 특히 부모 범위에서 읽기 전용으로 선언된 지역 변수를 선언하는 것을 허용하지 않는다는 점을 $IFS고려하면 문제가 발생할 수 있습니다 . 따라서 이를 수행하는 함수라도 파괴하다.bashlocal IFS=,

배열 요소에 문자열이 있는지 확인하는 방법에 대한 보다 일반적인 질문에 대해서는 bash(zsh와 달리) 연산자가 없지만 루프를 사용하여 쉽게 구현할 수 있습니다.

amongst() {
  local string needle="$1"
  shift
  for string do
    [[ $needle = "$string" ]] && return
  done
  false
}

그런 다음:

do_work {
  if amongst "$1" "${choices[@]}"; then
    echo 'yes!'
  else
    echo 'no!'
  fi
}

문자열을 찾는 데 더 적합한 구조는 해시 테이블이나 연관 배열을 사용하는 것입니다.

typeset -A choices=( [foo]=1 [bar]=1 [baz]=1 )
do_work() {
  if [[ -n ${choices[+$1]} ]]; then
    echo 'yes!'
  else
    echo 'no!'
  fi
}
complete -W "${!choices[*]}" do_work

이는 "${!choices[*]}"연관 배열의 키를 $IFS해당 지점의 첫 번째 문자와 연결합니다(설정되었지만 비어 있는 경우 구분 기호가 사용되지 않음).

Bash 연관 배열은 빈 키를 가질 수 없으므로 빈 문자열은 선택 사항 중 하나가 될 수 없지만 complete -W어쨌든 지원되지 않으며 사용자가 목록 완성을 제외하고는 빈 문자열을 완성하는 것은 별로 유용하지 않습니다. 허용 가능한 값 중

답변2

문의 값은 구문의 일부이므로 명시 case적으로 구분해야 합니다 . 다만, Case문의 값은 반드시 패턴이어야 하므로 확장 패턴을 사용할 수도 있다.||

string="*@($(IFS="|";echo "${choices[*]}"))*"다음과 같이 확장 스키마로 생성 하여 사용합니다.

#!/bin/bash

declare -ar choices=('foo' 'bar' 'baz')

string="*@($(IFS="|";echo "${choices[*]}"))*"    # value contains one option
#string="@($(IFS="|";echo "${choices[*]}"))"     # value is exactly one option

shopt -s extglob
function do_work {
  case "$1" in 
    $string)   echo 'yes!' ;;
    *)         echo 'no!'  ;;
  esac
}

complete -W "${choices[*]}" do_work

그러나 이것은 extglob의 옵션을 변경하고 있으며, 종료하기 전에 스크립트를 시작할 때의 값으로 반환하여 피할 수 있습니다. $string함수가 실행될 때가 아니라 함수가 정의될 ​​때 값이 확장된다는 점에 유의하세요 . 그러나 더 간단한 해결책은 =테스트의 오른쪽이 내부에서 작동한다는 [[...]]사실을 사용하여 해당 셸 옵션을 설정하지 않고 설정을 해제하는 것입니다.extglob 쉘 옵션이 활성화된 것과 마찬가지로(아니요, 죄송합니다. 에서는 작동하지 않습니다 [...].) 따라서 케이스 표현을 [[ $1 = $string ]]and(echo 명령의 경우)로 단순화할 수 있습니다. 이는 다음과 같이 작동합니다.

[[ $1 = $string ]] && echo 'yes!' || echo 'no!'

그러나 일반적으로 함수를 사용하는 것이 더 좋으며 함수가 종료 코드를 반환한다고 보장하지 않으므로 0실제로 다음을 사용해야 합니다.

if [[ $1 = $string ]]; then yesaction; else noaction; fi

이 답변의 어려운 부분을 이미 해결했으므로 공백과 함께 옵션을 사용하도록 완성 기능을 활성화할 수도 있습니다. IFS가 옵션을 분할할 때 필요한 것은 ${complete_var}각 옵션을 따옴표로 묶고 공백으로 구분하는 것뿐입니다.

#!/bin/bash

declare -a choices=('foo' 'foo bar' 'baz')

option_string="@($(printf -v var "%s|"       "${choices[@]}"; printf '%s' "${var%?}" ))"
complete_var="$(   printf -v var "'\"%s\"' " "${choices[@]}"; printf '%s' "${var%?}"  )"

#printf '%s\n' "${option_string}" "${complete_var}"

yesaction() { echo 'yes!'; }
noaction () { echo 'no!' ; }

do_work () {
    if    [[ $1 == $option_string ]]
    then  yesaction
    else  noaction
    fi    
}

complete -W "${complete_var}" do_work

return 34;
exit

그러면 이러한 값이 함수 내에서 고정된 값으로 설정됩니다. choices읽기 전용으로 설정할 필요가 없습니다 . 그리고 옵션 내에 공백을 허용하세요.

.이는 쉘에서 스크립트를 get() 한다고 가정합니다 . 그것은 다음과 같습니다:

$ . ./script.sh
$ do_work <kbd>TAB</kbd>-<kbd>TAB</kbd>      # will show "foo" "bar" and "foo bar" as options.

내가 볼 수 있는 유일한 문제는 완료된 값이 큰따옴표 안에 표시된다는 것입니다 "foo". 하지만 제 생각에는 이것은 해결해야 할 문제가 아닙니다.

답변3

=~연산자를 사용하여 값이 배열에 있는지 확인할 수도 있습니다 .

if [[ " ${choices[*]} " =~ " ${1} " ]]; then
    echo "yes!";
else
    echo "no!";
fi

관련 정보