In Bash, what is the simplest way to test if an array contains a certain value?
Using parameter expansion:
${parameter:+word} If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.
declare -A myarray
myarray[hello]="world"
for i in hello goodbye 123
do
if [ ${myarray[$i]:+_} ]
then
echo ${!myarray[$i]} ${myarray[$i]}
else
printf "there is no %s\n" $i
fi
done
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
Borrowing from Dennis Williamson's answer, the following solution combines arrays, shell-safe quoting, and regular expressions to avoid the need for: iterating over loops; using pipes or other sub-processes; or using non-bash utilities.
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
The above code works by using Bash regular expressions to match against a stringified version of the array contents. There are six important steps to ensure that the regular expression match can't be fooled by clever combinations of values within the array:
printf
shell-quoting, %q
. Shell-quoting will ensure that special characters become "shell-safe" by being escaped with backslash \
.%q
; that's the only way to guarantee that values within the array can't be constructed in clever ways to fool the regular expression match. I choose comma ,
because that character is the safest when eval'd or misused in an otherwise unexpected way.,,%q
as the argument to printf
. This is important because two instances of the special character can only appear next to each other when they appear as the delimiter; all other instances of the special character will be escaped.${array_str}
, compare against ${array_str},,
.Here's my take on this.
I'd rather not use a bash for loop if I can avoid it, as that takes time to run. If something has to loop, let it be something that was written in a lower level language than a shell script.
function array_contains { # arrayname value
local -A _arr=()
local IFS=
eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
return $(( 1 - 0${_arr[$2]} ))
}
This works by creating a temporary associative array, _arr
, whose indices are derived from the values of the input array. (Note that associative arrays are available in bash 4 and above, so this function won't work in earlier versions of bash.) We set $IFS
to avoid word splitting on whitespace.
The function contains no explicit loops, though internally bash steps through the input array in order to populate printf
. The printf format uses %q
to ensure that input data are escaped such that they can safely be used as array keys.
$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$
Note that everything this function uses is a built-in to bash, so there are no external pipes dragging you down, even in the command expansion.
And if you don't like using eval
... well, you're free to use another approach. :-)
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=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS
value="Jack"
if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
echo "true"
else
echo "false"
fi
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"
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }
Now handles empty arrays correctly.