In Bash, what is the simplest way to test if an array contains a certain value?
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"
Here is my take on this problem. Here is the short version:
function arrayContains() {
local haystack=${!1}
local needle="$2"
printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}
And the long version, which I think is much easier on the eyes.
# 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$"
}
Examples:
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
A small addition to @ghostdog74's answer about using case
logic to check that array contains particular value:
myarray=(one two three)
word=two
case "${myarray[@]}" in ("$word "*|*" $word "*|*" $word") echo "found" ;; esac
Or with extglob
option turned on, you can do it like this:
myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac
Also we can do it with if
statement:
myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi
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"
The following code checks if a given value is in the array and returns its zero-based offset:
A=("one" "two" "three four")
VALUE="two"
if [[ "$(declare -p A)" =~ '['([0-9]+)']="'$VALUE'"' ]];then
echo "Found $VALUE at offset ${BASH_REMATCH[1]}"
else
echo "Couldn't find $VALUE"
fi
The match is done on the complete values, therefore setting VALUE="three" would not match.
I had the case that I had to check if an ID was contained in a list of IDs generated by another script / command. For me worked the following:
# 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.
You could also shorten / compact it like this:
if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
echo "not found"
fi
In my case, I was running jq to filter some JSON for a list of IDs and had to later check if my ID was in this list and this worked the best for me.
It will not work for manually created arrays of the type LIST=("1" "2" "4")
but for with newline separated script output.
PS.: could not comment an answer because I'm relatively new ...