检查 Bash 数组中是否存在元素

我想知道是否有一种有效的方法来检查一个元素是否存在于 Bash 的数组中?我正在寻找一些类似于我在 Python 中可以做的事情,比如:

arr = ['a','b','c','d']


if 'd' in arr:
do your thing
else:
do something

我已经看到过一些解决方案,在 Bash 4 + 中使用 Bash 的关联数组,但我想知道是否还有其他的解决方案。

请理解,我知道简单的解决方案是在数组中迭代,但我不希望这样。

190309 次浏览

Obvious caveats aside, if your array was actually like the one above, you could do

if [[ ${arr[*]} =~ d ]]
then
do your thing
else
do something
fi

You could do:

if [[ " ${arr[*]} " == *" d "* ]]; then
echo "arr contains d"
fi

This will give false positives for example if you look for "a b" -- that substring is in the joined string but not as an array element. This dilemma will occur for whatever delimiter you choose.

The safest way is to loop over the array until you find the element:

array_contains () {
local seeking=$1; shift
local in=1
for element; do
if [[ $element == "$seeking" ]]; then
in=0
break
fi
done
return $in
}


arr=(a b c "d e" f g)
array_contains "a b" "${arr[@]}" && echo yes || echo no    # no
array_contains "d e" "${arr[@]}" && echo yes || echo no    # yes

Here's a "cleaner" version where you just pass the array name, not all its elements

array_contains2 () {
local array="$1[@]"
local seeking=$2
local in=1
for element in "${!array}"; do
if [[ $element == "$seeking" ]]; then
in=0
break
fi
done
return $in
}


array_contains2 arr "a b"  && echo yes || echo no    # no
array_contains2 arr "d e"  && echo yes || echo no    # yes

For associative arrays, there's a very tidy way to test if the array contains a given key: The -v operator

$ declare -A arr=( [foo]=bar [baz]=qux )
$ [[ -v arr[foo] ]] && echo yes || echo no
yes
$ [[ -v arr[bar] ]] && echo yes || echo no
no

See 6.4 Bash Conditional Expressions in the manual.

Here's another way that might be faster, in terms of compute time, than iterating. Not sure. The idea is to convert the array to a string, truncate it, and get the size of the new array.

For example, to find the index of 'd':

arr=(a b c d)
temp=`echo ${arr[@]}`
temp=( ${temp%%d*} )
index=${#temp[@]}

You could turn this into a function like:

get-index() {


Item=$1
Array="$2[@]"


ArgArray=( ${!Array} )
NewArray=( ${!Array%%${Item}*} )


Index=${#NewArray[@]}


[[ ${#ArgArray[@]} == ${#NewArray[@]} ]] && echo -1 || echo $Index


}

You could then call:

get-index d arr

and it would echo back 3, which would be assignable with:

index=`get-index d arr`

FWIW, here's what I used:

expr "${arr[*]}" : ".*\<$item\>"

This works where there are no delimiters in any of the array items or in the search target. I didn't need to solve the general case for my applicaiton.

If array elements don't contain spaces, another (perhaps more readable) solution would be:

if echo ${arr[@]} | grep -q -w "d"; then
echo "is in array"
else
echo "is not in array"
fi
array=("word" "two words") # let's look for "two words"

using grep and printf:

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

using for:

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

For not_found results add || <run_your_if_notfound_command_here>

  1. Initialize array arr and add elements
  2. set variable to search for SEARCH_STRING
  3. check if your array contains element
    arr=()
arr+=('a')
arr+=('b')
arr+=('c')
   

SEARCH_STRING='b'


if [[ " ${arr[*]} " == *"$SEARCH_STRING"* ]];
then
echo "YES, your arr contains $SEARCH_STRING"
else
echo "NO, your arr does not contain $SEARCH_STRING"
fi

As bash does not have a built-in value in array operator and the =~ operator or the [[ "${array[@]" == *"${item}"* ]] notation keep confusing me, I usually combine grep with a here-string:

colors=('black' 'blue' 'light green')
if grep -q 'black' <<< "${colors[@]}"
then
echo 'match'
fi

Beware however that this suffers from the same false positives issue as many of the other answers that occurs when the item to search for is fully contained, but is not equal to another item:

if grep -q 'green' <<< "${colors[@]}"
then
echo 'should not match, but does'
fi

If that is an issue for your use case, you probably won't get around looping over the array:

for color in "${colors[@]}"
do
if [ "${color}" = 'green' ]
then
echo "should not match and won't"
break
fi
done


for color in "${colors[@]}"
do
if [ "${color}" = 'light green' ]
then
echo 'match'
break
fi
done