Is there a nice way of checking if an array has an element in bash (better than looping through)?
Alternatively, is there another way to check if a number or string equals any of a set of predefined constants?
6 Answers
In Bash 4, you can use associative arrays:
# set up array of constants
declare -A array
for constant in foo bar baz
do array[$constant]=1
done
# test for existence
test1="bar"
test2="xyzzy"
if [[ ${array[$test1]} ]]; then echo "Exists"; fi # Exists
if [[ ${array[$test2]} ]]; then echo "Exists"; fi # doesn'tTo set up the array initially you could also do direct assignments:
array[foo]=1
array[bar]=1
# etc.or this way:
array=([foo]=1 [bar]=1 [baz]=1) 5 It's an old question, but I think what is the simplest solution has not appeared yet: test ${array[key]+_}. Example:
declare -A xs=([a]=1 [b]="")
test ${xs[a]+_} && echo "a is set"
test ${xs[b]+_} && echo "b is set"
test ${xs[c]+_} && echo "c is set"Outputs:
a is set
b is setTo see how this work check this.
2There is a way to test if an element of an associative array exists (not set), this is different from empty:
isNotSet() { if [[ ! ${!1} && ${!1-_} ]] then return 1 fi
}Then use it:
declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then echo "${KEY} is not set."
fi 3 You can see if an entry is present by piping the contents of the array to grep.
printf "%s\n" "${mydata[@]}" | grep "^${val}$"You can also get the index of an entry with grep -n, which returns the line number of a match (remember to subtract 1 to get zero-based index) This will be reasonably quick except for very large arrays.
# given the following data
mydata=(a b c "hello world")
for val in a c hello "hello world"
do # get line # of 1st matching entry ix=$( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 ) if [[ -z $ix ]] then echo $val missing else # subtract 1. Bash arrays are zero-based, but grep -n returns 1 for 1st line, not 0 echo $val found at $(( ix-1 )) fi
done
a found at 0
c found at 2
hello missing
hello world found at 3explanation:
$( ... )is the same as using backticks to capture output of a command into a variableprintfoutputs mydata one element per line- (all quotes necessary, along with
@instead of*.this avoids splitting "hello world" into 2 lines) grepsearches for exact string:^and$match beginning and end of linegrep -nreturns line #, in form of 4:hello worldgrep -m 1finds first match onlycutextracts just the line number- subtract 1 from returned line number.
You can of course fold the subtraction into the command. But then test for -1 for missing:
ix=$(( $( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 ) - 1 ))
if [[ $ix == -1 ]]; then echo missing; else ... fi$(( ... ))does integer arithmetic
#!/bin/bash
function in_array { ARRAY=$2 for e in ${ARRAY[*]} do if [[ "$e" == "$1" ]] then return 0 fi done return 1
}
my_array=(Drupal Wordpress Joomla)
if in_array "Drupal" "${my_array[*]}" then echo "Found" else echo "Not found"
fi 2 I don't think you can do it properly without looping unless you have very limited data in the array.
Here is one simple variant, this would correctly say that "Super User" exists in the array. But it would also say that "uper Use" is in the array.
MyArray=('Super User' 'Stack Overflow' 'Server Fault' 'Jeff' );
FINDME="Super User"
FOUND=`echo ${MyArray[*]} | grep "$FINDME"`
if [ "${FOUND}" != "" ]; then echo Array contains: $FINDME
else echo $FINDME not found
fi
#
# If you where to add anchors < and > to the data it could work
# This would find "Super User" but not "uper Use"
#
MyArray2=('<Super User>' '<Stack Overflow>' '<Server Fault>' '<Jeff>' );
FOUND=`echo ${MyArray2[*]} | grep "<$FINDME>"`
if [ "${FOUND}" != "" ]; then echo Array contains: $FINDME
else echo $FINDME not found
fiThe problem is that there is no easy way to add the anchors (that I can think of) besides looping through the array. Unless you can add them before you put them in the array...
1