Checking if an array contains an element in bash

meleu - Dec 12 '20 - - Dev Community

When working with arrays it's quite common to face the need to check if an array includes a certain element. Although we can have arrays in bash, we don't have a specific method to check that. In this article we're going to address this problem using a not so known bash feature.

TL;DR

If you just want the solution and don't care about how it works, here it is (the explanation comes right after):

#!/usr/bin/env bash

joinByChar() {
  local IFS="$1"
  shift
  echo "$*"
}

# if extglob is not enabled, uncomment the line below
# shopt -s extglob
# The function returns status 0 if the array contains the element
elementInArray() {
  local element="$1"
  shift
  local array=("$@")
  [[ "$element" == @($(joinByChar '|' "${array[@]//|/\\|}")) ]]
}
Enter fullscreen mode Exit fullscreen mode

Explanation

extglob

We're going to use the shell option extglob, which stands for Extended Globbing. This feature was implemented in bash in version 2.02 (1998), so, unless you're using a 20 year old system, this method is pretty portable.

With extglob we have a richer way to specify sets of filenames (aka globbing). There are many cool things you can do with this feature. Here in this article we're going to use just a single part of it.

In order to use the method we're going to explain here the shell option extglob must be enabled. It seems to be the default nowadays, but to be sure, let's check it:

$ shopt extglob
extglob         on

$ # if it was off, you could turn it on with the following command

$ shopt -s extglob

$ # the -s option stands for *set* the option (turn it on)
Enter fullscreen mode Exit fullscreen mode

Once that option is enabled, we can search for a string inside a list where each element is separated by a | pipe. Example:

[[ $element == @(element1|element2|elementN) ]]
Enter fullscreen mode Exit fullscreen mode

Let's a practical example:

$ [[ one == @(one|two|three) ]] && echo yes || echo no
yes

$ [[ four == @(one|two|three) ]] && echo yes || echo no
no
Enter fullscreen mode Exit fullscreen mode

Pretty simple, isn't it? In order to use this feature to check if an element is present in an array, first we need to get each element of the array and separate them with a | pipe. So, let's use a function to help us with that.

joinByChar()

We addressed this problem in a previous article where we created the joinByChar() function. The function is pretty short (you can find the explanation of how it works in that article):

joinByChar() {
  local IFS="$1"
  shift
  echo "$*"
}
Enter fullscreen mode Exit fullscreen mode

Nice, now looks like we can do this:

#!/usr/bin/env bash
# elementInArray.sh
###################

joinByChar() {
  local IFS="$1"
  shift
  echo "$*"
}

# if extglob is not enabled, uncomment the line below
# shopt -s extglob
elementInArray() {
  local element="$1"
  shift
  local array=("$@")
  [[ "$element" == @($(joinByChar '|' "${array[@]}")) ]]
}
Enter fullscreen mode Exit fullscreen mode

Let's test it:

$ source elementInArray.sh 

$ array1=(one two 'three|four' 'five six')

$ elementInArray ten "${array1[@]}"

$ elementInArray one "${array1[@]}" && echo yes || echo no
yes

$ elementInArray two "${array1[@]}" && echo yes || echo no
yes

$ elementInArray three "${array1[@]}" && echo yes || echo no
yes

$ # wait! I don't have an element 'three' in that array
Enter fullscreen mode Exit fullscreen mode

Uhm... That | in the element three|four "confused" our function. We're going to need more bash tricks here.

Let's use the variable substring replacement to escape our | pipes.

Escaping the pipes

The construction we're using here is sometimes called global replacement, and it works like this:

${variable//pattern/replacement}
Enter fullscreen mode Exit fullscreen mode

Where all matches of pattern, within variable is replaced with replacement.

We're going to use this: ${array[@]//|/\\|}

Which means "replace every ocurrence of | found in the elements of array with \|".

So, our final solution will be this one:

#!/usr/bin/env bash
# elementInArray.sh
###################

joinByChar() {
  local IFS="$1"
  shift
  echo "$*"
}

# if extglob is not enabled, uncomment the line below
# shopt -s extglob
# The function returns status 0 if the array contains the element
elementInArray() {
  local element="$1"
  shift
  local array=("$@")
  [[ "$element" == @($(joinByChar '|' "${array[@]//|/\\|}")) ]]
}
Enter fullscreen mode Exit fullscreen mode

Let's see if it's really safe:

$ source elementInArray.sh 

$ term='watermelon'

$ fruits=(grape apple orange kiwi)

$ elementInArray "$term" "${fruits[@]}"

$ elementInArray "$term" "${fruits[@]}" && echo true || echo false
false

$ # let's make it easier to check the exit status with an alias:

$ alias result='echo true || echo false'

$ elementInArray "$term" "${fruits[@]}" && result
false

$ # let's try to confuse it with   s p a c e s

$ term='passion'

$ fruits=('passion fruit' apple orange kiwi)

$ elementInArray "$term" "${fruits[@]}" && result
false

$ term='passion fruit'

$ elementInArray "$term" "${fruits[@]}" && result
true

$ # let's try to confuse it with|pipes|too

$ array1=(one two 'three|four' 'five six')

$ elementInArray one "${array1[@]}" && result
true

$ elementInArray three "${array1[@]}" && result
false

$ # nice! we, indeed, don't have a 'three' element

$ elementInArray 'three|four' "${array1[@]}" && result
true

$ elementInArray 'five' "${array1[@]}" && result
false

$ elementInArray 'five six' "${array1[@]}" && result
true
Enter fullscreen mode Exit fullscreen mode

As you can see, this seems to be a nice method to check if an array contains an element.

Links

. . . . . . .
Terabox Video Player