Test if element is in array in bash

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?


Solution 1:

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't

To 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)

Solution 2:

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 set

To see how this work check this.

Solution 3:

There 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

Solution 4:

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 3

explanation:

  • $( ... ) is the same as using backticks to capture output of a command into a variable
  • printf outputs mydata one element per line
  • (all quotes necessary, along with @ instead of *. this avoids splitting "hello world" into 2 lines)
  • grep searches for exact string: ^ and $ match beginning and end of line
  • grep -n returns line #, in form of 4:hello world
  • grep -m 1 finds first match only
  • cut extracts 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