In Bash, what is the simplest way to test if an array contains a certain value?
41 Answers
This approach has the advantage of not needing to loop over all the elements (at least not explicitly). But since array_to_string_internal() in array.c still loops over array elements and concatenates them into a string, it's probably not more efficient than the looping solutions proposed, but it's more readable.
if [[ " ${array[*]} " =~ " ${value} " ]]; then
# whatever you want to do when array contains value
fi
if [[ ! " ${array[*]} " =~ " ${value} " ]]; then
# whatever you want to do when array doesn't contain value
fi
Note that in cases where the value you are searching for is one of the words in an array element with spaces, it will give false positives. For example
array=("Jack Brown")
value="Jack"
The regex will see "Jack" as being in the array even though it isn't. So you'll have to change IFS and the separator characters on your regex if you want still to use this solution, like this
IFS="|"
array=("Jack Brown${IFS}Jack Smith")
value="Jack"
if [[ "${IFS}${array[*]}${IFS}" =~ "${IFS}${value}${IFS}" ]]; then
echo "true"
else
echo "false"
fi
unset IFS # or set back to original IFS if previously set
This will print "false".
Obviously this can also be used as a test statement, allowing it to be expressed as a one-liner
[[ " ${array[*]} " =~ " ${value} " ]] && echo "true" || echo "false"
Below is a small function for achieving this. The search string is the first argument and the rest are the array elements:
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
A test run of that function could look like:
$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
One-line solution
printf '%s\0' "${myarray[@]}" | grep -F -x -z 'myvalue'
Explanation
The printf statement prints each element of the array, delimited by null characters.
The grep statement uses the following flags to match an item that contains exactly the string given as myvalue (no more, no less):
-z/--null-data- Lines are terminated by a zero byte instead of a newline.-F/--fixed-strings- Interpret PATTERNS as fixed strings, not regular expressions.-x/--line-regexp- Select only those matches that exactly match the whole line.
Why do we use a null byte \0 instead of a newline \n? Your array may actually contain newlines within its elements. (If you know that it doesn't, feel free to drop the -z grep option and substitude %s\n as your first printf arg.)
Usage
To put this into an if ... then statement:
if printf '%s\0' "${myarray[@]}" | grep -Fxqz 'myvalue'; then
# ...
fi
I added a -q flag to the grep expression so that it won't print matches; it will just treat the existence of a match as "true."
Update: Thanks, presto8, for pointing out the --line-regexp flag. Thanks, Tino, for pointing out the case where newlines can exist within array items.
for i in "${array[@]}"
do
if [ "$i" -eq "$yourValue" ] ; then
echo "Found"
fi
done
For strings:
for i in "${array[@]}"
do
if [ "$i" == "$yourValue" ] ; then
echo "Found"
fi
done
$ myarray=(one two three)
$ case "${myarray[@]}" in *"two"*) echo "found" ;; esac
found
I typically just use:
inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)
non zero value indicates a match was found.
... actually, to solve the problem mentioned with it not working with needle1 and needle2, if you only want an exact match, nothing more, nothing less, just add a w after the -o for a whole word match
inarray=$(echo ${haystack[@]} | grep -ow "needle" | wc -w)
If you need performance, you don't want to loop over your whole array every time you search.
In this case, you can create an associative array (hash table, or dictionary) that represents an index of that array. I.e. it maps each array element into its index in the array:
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
}
Then you can use it like this:
myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"
And test membership like so:
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
Or also:
if [ "${myarray_index[$member]}" ]; then
echo FOUND
fi
Notice that this solution does the right thing even if the there are spaces in the tested value or in the array values.
As a bonus, you also get the index of the value within the array with:
echo "<< ${myarray_index[$member]} >> is the index of $member"
Another one liner without a function:
(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"
Thanks @Qwerty for the heads up regarding spaces!
corresponding function:
find_in_array() {
local word=$1
shift
for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
return 1
}
example:
some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }
Now handles empty arrays correctly.
If you want to do a quick and dirty test to see if it's worth iterating over the whole array to get a precise match, Bash can treat arrays like scalars. Test for a match in the scalar, if none then skipping the loop saves time. Obviously you can get false positives.
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
This will output "Checking" and "Match". With array=(word "two words" something) it will only output "Checking". With array=(word "two widgets" something) there will be no output.
Here is a small contribution :
array=(word "two words" words)
search_string="two"
match=$(echo "${array[@]:0}" | grep -o $search_string)
[[ ! -z $match ]] && echo "found !"
Note: this way doesn't distinguish the case "two words" but this is not required in the question.
How to check if a Bash Array contains a value
False positive match
array=(a1 b1 c1 d1 ee)
[[ ${array[*]} =~ 'a' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'a1' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'e' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'ee' ]] && echo 'yes' || echo 'no'
# output:
yes