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アソシエーション配列は空のキーを持つことができないため、空の文字列はオプションの1つにすることはできませんが、とにかくサポートされていませ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

関連情報