95

I was wondering if there is an efficient way to check if an element is present within an array in Bash? I am looking for something similar to what I can do in Python, like:

arr = ['a','b','c','d']

if 'd' in arr:
    do your thing
else:
    do something

I've seen solutions using associative array for bash for Bash 4+, but I am wondering if there is another solution out there.

Please understand that I know the trivial solution is to iterate in the array, but I don't want that.

2
  • 6
    Don't confuse "concise" with "efficient". But no, there is no concise way in bash to do what you want with a simple array. Commented Jan 16, 2013 at 19:43
  • Take a look at stackoverflow.com/questions/3685970/… Commented Jan 16, 2013 at 20:01

8 Answers 8

125

You could do:

if [[ " ${arr[*]} " == *" d "* ]]; then
    echo "arr contains d"
fi

This will give false positives for example if you look for "a b" -- that substring is in the joined string but not as an array element. This dilemma will occur for whatever delimiter you choose.

The safest way is to loop over the array until you find the element:

array_contains () {
    local seeking=$1; shift
    local in=1
    for element; do
        if [[ $element == "$seeking" ]]; then
            in=0
            break
        fi
    done
    return $in
}

arr=(a b c "d e" f g)
array_contains "a b" "${arr[@]}" && echo yes || echo no    # no
array_contains "d e" "${arr[@]}" && echo yes || echo no    # yes

Here's a "cleaner" version where you just pass the array name, not all its elements

array_contains2 () { 
    local array="$1[@]"
    local seeking=$2
    local in=1
    for element in "${!array}"; do
        if [[ $element == "$seeking" ]]; then
            in=0
            break
        fi
    done
    return $in
}

array_contains2 arr "a b"  && echo yes || echo no    # no
array_contains2 arr "d e"  && echo yes || echo no    # yes

For associative arrays, there's a very tidy way to test if the array contains a given key: The -v operator

$ declare -A arr=( [foo]=bar [baz]=qux )
$ [[ -v arr[foo] ]] && echo yes || echo no
yes
$ [[ -v arr[bar] ]] && echo yes || echo no
no

See 6.4 Bash Conditional Expressions in the manual.

Sign up to request clarification or add additional context in comments.

9 Comments

Does this code works both for numeric and string values ? For instance if my array contains both strings and array, can I search for both ?
yes. Bash will handle numeric values as strings without difficulty
In my implementation I had to change "${!array}" to "${array[@]}" to work. I judged the "array_contains" and the "array_contains2" proposals safer because many who use grep return false positives.
Consider quoting $seeking so that it's not considered a pattern.
The -v operator only works in Bash 4.3+. In earlier versions, test for a key using +_: declare -A says says[horse]=neigh says[fish]= [[ ${says[horse]+_} ]] && echo "horse: ${says[horse]}" || echo "horse doesn't exist" horse: neigh [[ ${says[fish]+_} ]] && echo "fish: ${says[fish]}" || echo "fish doesn't exist" fish: [[ ${says[unicorn]+_} ]] && echo "unicorn: ${says[unicorn]}" || echo "unicorn doesn't exist" unicorn doesn't exist Tested in v4.2.46. Source
|
39

Obvious caveats aside, if your array was actually like the one above, you could do

if [[ ${arr[*]} =~ d ]]
then
  do your thing
else
  do something
fi

4 Comments

+1 for teaching something new. But d will match xdy as well.
@OlafDietsche That could be solved by writing it if [[ ${arr[*]} =~ $(echo '\<d\>') ]] (source)
Thanks that was helpful. But how can I negate the outcome in case I don't have an else following? That would save some redundant code. @Steven Penny
@SvenM. if [[ ! ${arr[*]} =~ d ]] ?
32
  1. Initialize array arr and add elements
  2. set variable to search for SEARCH_STRING
  3. check if your array contains element
    arr=()
    arr+=('a')
    arr+=('b')
    arr+=('c')
   
    SEARCH_STRING='b'

    if [[ " ${arr[*]} " == *"$SEARCH_STRING"* ]];
    then
        echo "YES, your arr contains $SEARCH_STRING"
    else
        echo "NO, your arr does not contain $SEARCH_STRING"
    fi

4 Comments

This was the only reply in the thread that worked for me, thank you!
This is fantastically concise and I was able to use this with BASH_ARGV to test whether a certain word was given as an argument that requires a different one-off setup was present without having to do some kind of argparse which is overkill for this case.
This is the only answer across StackOverflow that I found to be working.
A blog had almost this, but with spaces outside $SEARCH_STRING which did not work. I had to remove those spaces (and it also works if I remove the spaces inside the quotes on the left side).
22

If array elements don't contain spaces, another (perhaps more readable) solution would be:

if echo ${arr[@]} | grep -q -w "d"; then 
    echo "is in array"
else 
    echo "is not in array"
fi

5 Comments

+1. Nice answer. This solution works for me to check whether a number is existed in a number array. While @Steven Penny's solution didn't work 100% correct in my case for number array. if echo ${combined_p2_idx[@]} | grep -q -w $subset_id; then
I agree with @GoodWill. This should be the accepted answer. Other options weren't as robust.
It will error if you have negative numbers in the array
add -- to signal the end of parameters and negative number will not treated as a param for grep
By far, the most elegant and understandable solution. Worked like a charm for me.
4
array=("word" "two words") # let's look for "two words"

using grep and printf:

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

using for:

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

For not_found results add || <run_your_if_notfound_command_here>

1 Comment

@Querty I don’t know why this isn’t the top answer. grep -x... makes it the simplest to use.
4

As bash does not have a built-in value in array operator and the =~ operator or the [[ "${array[@]" == *"${item}"* ]] notation keep confusing me, I usually combine grep with a here-string:

colors=('black' 'blue' 'light green')
if grep -q 'black' <<< "${colors[@]}"
then
    echo 'match'
fi

Beware however that this suffers from the same false positives issue as many of the other answers that occurs when the item to search for is fully contained, but is not equal to another item:

if grep -q 'green' <<< "${colors[@]}"
then
    echo 'should not match, but does'
fi

If that is an issue for your use case, you probably won't get around looping over the array:

for color in "${colors[@]}"
do
    if [ "${color}" = 'green' ]
    then
        echo "should not match and won't"
        break
    fi
done

for color in "${colors[@]}"
do
    if [ "${color}" = 'light green' ]
    then
        echo 'match'
        break
    fi
done

1 Comment

@sec all that’s needed as @Querty’s answer above demonstrated to avoid false positives is to add -x to grep to match the entire line.
2

Here's another way that might be faster, in terms of compute time, than iterating. Not sure. The idea is to convert the array to a string, truncate it, and get the size of the new array.

For example, to find the index of 'd':

arr=(a b c d)    
temp=`echo ${arr[@]}`
temp=( ${temp%%d*} )
index=${#temp[@]}

You could turn this into a function like:

get-index() {

    Item=$1
    Array="$2[@]"

    ArgArray=( ${!Array} )
    NewArray=( ${!Array%%${Item}*} )

    Index=${#NewArray[@]}

    [[ ${#ArgArray[@]} == ${#NewArray[@]} ]] && echo -1 || echo $Index

}

You could then call:

get-index d arr

and it would echo back 3, which would be assignable with:

index=`get-index d arr`

Comments

0

FWIW, here's what I used:

expr "${arr[*]}" : ".*\<$item\>"

This works where there are no delimiters in any of the array items or in the search target. I didn't need to solve the general case for my applicaiton.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.