web-dev-qa-db-ja.com

Bash配列に値が含まれているかどうかを確認

Bashでは、配列に特定の値が含まれているかどうかをテストするための最も簡単な方法は何ですか?

編集 :いくつかのテストの後、答えとコメントの助けを借りて、私はこれを思い付きました:

function contains() {
    local n=$#
    local value=${!n}
    for ((i=1;i < $#;i++)) {
        if [ "${!i}" == "${value}" ]; then
            echo "y"
            return 0
        fi
    }
    echo "n"
    return 1
}

A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
    echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
    echo "contains three"
fi

それが最善の解決策かどうかはわかりませんが、うまくいくようです。

331
Paolo Tedesco

配列から部分文字列を置き換える方法を示す サンプルコードがあります 。配列のコピーを作成し、そのコピーからターゲット値を削除しようとすることができます。コピーとオリジナルが異なる場合、ターゲット値はオリジナルの文字列に存在します。

簡単な(しかしもっと時間がかかる可能性がある)解決策は、単純に配列全体を繰り返し処理して各項目を個別にチェックすることです。これは、実装が簡単で、関数で囲むことができるので、私が通常することです( 配列を関数に渡すときのこの情報 を参照)。

10
bta

以下はこれを達成するための小さな機能です。検索文字列は最初の引数で、残りは配列要素です。

containsElement () {
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

その機能のテスト実行は次のようになります。

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
323
patrik

この方法には、(少なくとも明示的にではなく)すべての要素をループ処理する必要がないという利点があります。しかし、 array.carray_to_string_internal()は依然として配列要素をループしてそれらを文字列に連結するので、おそらく提案されているループ解決策より効率的ではありませんが、より読みやすくなります。

if [[ " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when arr contains value
fi

if [[ ! " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when arr doesn't contain value
fi

検索している値がスペースを含む配列要素内の単語の1つである場合は、誤検出が発生することに注意してください。例えば

array=("Jack Brown")
value="Jack"

正規表現では、そうでなくてもJackが配列内にあると見なします。それで、この解決法を使い続けたいのなら、あなたは正規表現のIFSとセパレータ文字を変更しなければならないでしょう。

IFS=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS

value="Jack Smith"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "yep, it's there"
fi
311
Keegan
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
59
ghostdog74
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

文字列の場合:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done
38
Scott

パフォーマンスが必要な場合は、検索するたびに配列全体をループする必要はありません。

この場合、その配列のインデックスを表す連想配列(ハッシュテーブル、または辞書)を作成できます。すなわち各配列要素を配列内のそのインデックスにマッピングします。

make_index () {
  local index_name=$1
  shift
  local -a value_array=("$@")
  local i
  # -A means associative array, -g means create a global variable:
  declare -g -A ${index_name}
  for i in "${!value_array[@]}"; do
    eval ${index_name}["${value_array[$i]}"]=$i
  done
}

それからあなたはこのようにそれを使うことができます:

myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"

そしてメンバーシップを次のようにテストします。

member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND

または:

if [ "${myarray_index[$member]}" ]; then 
  echo FOUND
fi

テストされた値または配列の値にスペースがあっても、この解決策は正しいことをします。

おまけとして、配列内の値のインデックスも取得できます。

echo "<< ${myarray_index[$member]} >> is the index of $member"
17
LeoRochael

私は通常単に使用します:

inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)

ゼロ以外の値は、一致が見つかったことを示します。

16
Sean DiSanti

ワンラインソリューション

printf '%s\n' ${myarray[@]} | grep -P '^mypattern$'

説明

printfステートメントは、配列の各要素を別々の行に表示します。

grepステートメントは、特殊文字^$を使用して、 正確に mypatternとして指定されたパターンを含む行を検索します(これ以上、それ以上でもありません)。


使用法

これをif ... thenステートメントに入れるには:

if printf '%s\n' ${myarray[@]} | grep -q -P '^mypattern$'; then
    # ...
fi

一致を表示しないように、grep式に-qフラグを追加しました。それは単に一致の存在を「真」として扱うでしょう。

13
JellicleCat

機能のないもう一つのライナー:

(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo found || not found

スペースに関して頭を上げてくれてありがとう@Qwerty!

対応する機能:

find_in_array() {
  local Word=$1
  shift
  for e in "$@"; do [[ "$e" == "$Word" ]] && return 0; done
}

例:

some_words=( these are some words )
find_in_array Word "${some_words[@]}" || echo "expected missing! since words != Word"
11
estani

これはちょっとした貢献です。

array=(Word "two words" words)  
search_string="two"  
match=$(echo "${array[@]:0}" | grep -o $search_string)  
[[ ! -z $match ]] && echo "found !"  

注:この方法では「2語」というケースは区別されませんが、これは問題では必要ありません。

10
hornetbzz
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

空の配列を正しく処理します。

9
Yann

正確な一致を得るために配列全体を反復処理する価値があるかどうかを確認するために、迅速で汚いテストを行いたい場合、Bashは配列をスカラーのように扱うことができます。スカラの一致をテストします。何もない場合は、ループをスキップすることで時間を節約できます。明らかにあなたは誤検知を受けることができます。

array=(Word "two words" words)
if [[ ${array[@]} =~ words ]]
then
    echo "Checking"
    for element in "${array[@]}"
    do
        if [[ $element == "words" ]]
        then
            echo "Match"
        fi
    done
fi

これにより、 "Checking"と "Match"が出力されます。 array=(Word "two words" something)では、 "Checking"のみを出力します。 array=(Word "two widgets" something)の場合は何も出力されません。

6
a=(b c d)

if printf '%s\0' "${a[@]}" | grep -Fqxz c
then
  echo 'array “a” contains value “c”'
fi

ご希望の場合は、同等の長いオプションを使用できます。

--fixed-strings --quiet --line-regexp --null-data
5
Steven Penny

これは私のために働いています:

# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
    # odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
    local list=$1[@]
    local elem=$2

    # echo "list" ${!list}
    # echo "elem" $elem

    for i in "${!list}"
    do
        # echo "Checking to see if" "$i" "is the same as" "${elem}"
        if [ "$i" == "${elem}" ] ; then
            # echo "$i" "was the same as" "${elem}"
            return 0
        fi
    done

    # echo "Could not find element"
    return 1
}

呼び出し例

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi
4
Chris Prince

与えられた:

array=("something to search for" "a string" "test2000")
elem="a string"

それから:

if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[@]/#/${c}} ${c}" =~ $p ]]; then
  echo "$elem exists in array"
fi

どこで

c is element separator
p is regex pattern

([[]]の中で直接式を使用するのではなく、pを別々に割り当てる理由は、bash 4との互換性を維持するためです)

3
Beorn Harris

grepprintfを使う

各配列メンバーを新しい行にフォーマットしてから、その行をgrepにします。

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
$ array=("Word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true

区切り文字とスペースに問題がないことに注意してください。

2
Qwerty

Dennis Williamsonanswer から借りて、以下の解決法は、配列、シェルセーフな引用、および正規表現を組み合わせて、以下の必要性を回避します。パイプまたは他のサブプロセスを使用する。またはbash以外のユーティリティを使用する。

declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[@]}"

if [[ "${array_str},," =~ ,,words,, ]]
then
   echo 'Matches'
else
   echo "Doesn't match"
fi

上記のコードは、文字列化されたバージョンの配列の内容と照合するためにBash正規表現を使用することによって機能します。正規表現の一致が配列内の値の巧妙な組み合わせによってだまされないようにするための6つの重要なステップがあります。

  1. Bashに組み込まれているprintfシェルクォート%qを使用して比較文字列を作成します。シェルクォーテーションは、バックスラッシュ\でエスケープされることによって、特殊文字が「シェルセーフ」になることを保証します。
  2. 値の区切り文字として機能する特殊文字を選択してください。 %qを使用すると、区切り文字はエスケープされる特殊文字の1つになります。これが、配列内の値が正規表現の一致を欺くための賢い方法で構築できないことを保証する唯一の方法です。私はカンマ,を選びました。なぜなら、その文字は他の方法で予想外の方法で評価または誤用されたときに最も安全だからです。
  3. 区切り文字として機能する特殊文字の two インスタンスを使用して、すべての配列要素を単一の文字列に結合します。例としてカンマを使用して、私はprintfへの引数として,,%qを使用しました。特殊文字の2つのインスタンスは、区切り文字として表示されている場合にのみ隣接して表示されるため、これは重要です。特殊文字の他のすべてのインスタンスはエスケープされます。
  4. 配列の最後の要素に対する一致を許可するために、区切り文字の末尾の2つのインスタンスを文字列に追加します。したがって、${array_str}と比較する代わりに、${array_str},,と比較します。
  5. 探しているターゲット文字列がユーザー変数によって提供されている場合は、 必須 の特殊文字のすべてのインスタンスをバックスラッシュでエスケープする必要があります。そうでなければ、正規表現のマッチは巧妙に細工された配列要素によってだまされることに対して脆弱になります。
  6. 文字列に対してBash正規表現の一致を実行します。
2
Dejay Clayton

私は一般的にこれらの種類のユーティリティは変数の値ではなく変数の名前で動作するように書く。これは主にbashが変数を参照渡しできないからである。

これは配列の名前で動作するバージョンです。

function array_contains # array value
{
    [[ -n "$1" && -n "$2" ]] || {
        echo "usage: array_contains <array> <value>"
        echo "Returns 0 if array contains value, 1 otherwise"
        return 2
    }

    eval 'local values=("${'$1'[@]}")'

    local element
    for element in "${values[@]}"; do
        [[ "$element" == "$2" ]] && return 0
    done
    return 1
}

これにより、質問の例は次のようになります。

array_contains A "one" && echo "contains one"

等.

2
Barry Kelly

答えた後、私は私が特に好きだったもう一つの答えを読みました、しかしそれは欠陥がありそして軽蔑されました。私はインスピレーションを得ました、そしてここに私が実行可能に見える2つの新しいアプローチがあります。

array=("Word" "two words") # let's look for "two words"

grepprintfを使う:

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

forを使う:

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

not_foundの場合は|| <run_your_if_notfound_command_here>を追加

1
Qwerty

これが私の考えです。

回避することができれば、実行に時間がかかるため、bash for loopを使用しないでください。何かループする必要がある場合は、シェルスクリプトよりも低レベルの言語で書かれたものにします。

function array_contains { # arrayname value
  local -A _arr=()
  local IFS=
  eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
  return $(( 1 - 0${_arr[$2]} ))
}

これは、一時的な連想配列_arrを作成することによって機能します。そのインデックスは入力配列の値から派生します。 (連想配列はbash 4以降で利用できるので、この関数はbashの以前のバージョンでは動作しません。)空白でのWord分割を避けるために$IFSを設定します。

この関数には明示的なループは含まれていませんが、printfを生成するために内部的に入力配列をステップスルーします。 printfフォーマットは%qを使用して、入力データが配列キーとして安全に使用できるようにエスケープされるようにします。

$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$

この関数が使うことはすべてbashに組み込まれていることに注意してください。そのため、コマンド展開でも、外部のパイプがあなたをドラッグダウンすることはありません。

そしてevalを使うのが好きでなければ…他のアプローチを自由に使うことができます。 :-)

1
ghoti

ここに提示されたアイデアのいくつかを組み合わせると、ループなしの文が 完全に一致するWordが に一致する場合、エレガントにすることができます。

$find="myword"
$array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
  echo "Array contains myword";
fi

これはWordvalでは起動せず、Word全体が一致するだけです。各配列値に複数の単語が含まれていると壊れます。

1
Ecker00

配列に特定の値が含まれていることを確認するためにcaseロジックを使用することに関する@ ghostdog74の回答に少し追加しました。

myarray=(one two three)
Word=two
case "${myarray[@]}" in  ("$Word "*|*" $Word "*|*" $Word") echo "found" ;; esac

あるいはextglobオプションをオンにすると、次のようになります。

myarray=(one two three)
Word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$Word"?(" "*)) echo "found" ;; esac

またifステートメントでそれを行うことができます:

myarray=(one two three)
Word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$Word\]_.* ]]; then echo "found"; fi
1

私は、あるIDが他のスクリプトやコマンドによって生成されたIDのリストに含まれているかどうかをチェックしなければならないというケースがありました。私は次のように働きました。

# the ID I was looking for
ID=1

# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "

# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)

# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
    echo "not found"
fi
# etc.

このように短く/コンパクトにすることもできます。

if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
    echo "not found"
fi

私の場合は、IDのリスト用にいくつかのJSONをフィルタリングするためにjqを実行していて、後で自分のIDがこのリストに含まれているかどうかを確認する必要がありました。手動で作成されたLIST=("1" "2" "4")型の配列に対しては機能しませんが、改行で区切られたスクリプト出力に対しては機能します。


シモンズ:私は比較的新しいので答えをコメントできませんでした….

0
E. Körner

これが私のこの問題に対する考え方です。これがショートバージョンです。

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}

そして長いバージョンは、私にとってはずっと見やすいと思います。

# With added utility function.
function arrayToLines() {
        local array=${!1}
        printf "%s\n" ${array[@]}
}

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        arrayToLines haystack[@] | grep -q "^$needle$"
}

例:

test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False
0
robert