Easiest way to check for an index or a key in an array?

ArraysBashIndexingKey

Arrays Problem Overview


Using:

set -o nounset
  1. Having an indexed array like:

    myArray=( "red" "black" "blue" )
    

    What is the shortest way to check if element 1 is set?
    I sometimes use the following:

    test "${#myArray[@]}" -gt "1" && echo "1 exists" || echo "1 doesn't exist"
    

    I would like to know if there's a preferred one.

  2. How to deal with non-consecutive indexes?

    myArray=()
    myArray[12]="red"
    myArray[51]="black"
    myArray[129]="blue"
    

    How to quick check that 51 is already set for example?

  3. How to deal with associative arrays?

    declare -A myArray
    myArray["key1"]="red"
    myArray["key2"]="black"
    myArray["key3"]="blue"
    

    How to quick check that key2 is already used for example?

Arrays Solutions


Solution 1 - Arrays

To check if the element is set (applies to both indexed and associative array)

[ "${array[key]+abc}" ] && echo "exists"

Basically what ${array[key]+abc} does is

  • if array[key] is set, return abc
  • if array[key] is not set, return nothing


References:

  1. See Parameter Expansion in Bash manual and the little note > if the colon is omitted, the operator tests only for existence [of parameter]

  2. This answer is actually adapted from the answers for this SO question: How to tell if a string is not defined in a bash shell script?


A wrapper function:

exists(){
  if [ "$2" != in ]; then
    echo "Incorrect usage."
    echo "Correct usage: exists {key} in {array}"
    return
  fi   
  eval '[ ${'$3'[$1]+muahaha} ]'  
}

For example

if ! exists key in array; then echo "No such array element"; fi 

Solution 2 - Arrays

From man bash, conditional expressions:

-v varname
              True if the shell variable varname is set (has been assigned a value).

example:

declare -A foo
foo[bar]="this is bar"
foo[baz]=""
if [[ -v "foo[bar]" ]] ; then
  echo "foo[bar] is set"
fi
if [[ -v "foo[baz]" ]] ; then
  echo "foo[baz] is set"
fi
if [[ -v "foo[quux]" ]] ; then
  echo "foo[quux] is set"
fi

This will show that both foo[bar] and foo[baz] are set (even though the latter is set to an empty value) and foo[quux] is not.

Solution 3 - Arrays

New answer

From version 4.2 of [tag:bash] (and newer), there is a new -v option to built-in test command.

From version 4.3, this test could address element of arrays.

array=([12]="red" [51]="black" [129]="blue")

for i in 10 12 30 {50..52} {128..131};do
    if [ -v 'array[i]' ];then
        echo "Variable 'array[$i]' is defined"
    else
        echo "Variable 'array[$i]' not exist"
    fi
done

Variable 'array[10]' not exist Variable 'array[12]' is defined Variable 'array[30]' not exist Variable 'array[50]' not exist Variable 'array[51]' is defined Variable 'array[52]' not exist Variable 'array[128]' not exist Variable 'array[129]' is defined Variable 'array[130]' not exist Variable 'array[131]' not exist

Note: regarding ssc's comment, I've single quoted 'array[i]' in -v test, in order to satisfy shellcheck's error SC2208. This seem not really required here, because there is no glob character in array[i], anyway...

This work with associative arrays in same way:

declare -A aArray=([foo]="bar" [bar]="baz" [baz]=$'Hello world\041')

for i in alpha bar baz dummy foo test;do
    if [ -v 'aArray[$i]' ];then
        echo "Variable 'aArray[$i]' is defined"
    else
        echo "Variable 'aArray[$i]' not exist"
    fi
done

Variable 'aArray[alpha]' not exist Variable 'aArray[bar]' is defined Variable 'aArray[baz]' is defined Variable 'aArray[dummy]' not exist Variable 'aArray[foo]' is defined Variable 'aArray[test]' not exist

With a little difference:
In regular arrays, variable between brackets ([i]) is integer, so dollar symbol ($) is not required, but for associative array, as key is a word, $ is required ([$i])!

Old answer for [tag:bash] prior to V4.2

Unfortunately, bash give no way to make difference betwen empty and undefined variable.

But there is some ways:

$ array=()
$ array[12]="red"
$ array[51]="black"
$ array[129]="blue"

$ echo ${array[@]}
red black blue

$ echo ${!array[@]}
12 51 129

$ echo "${#array[@]}"
3

$ printf "%s\n" ${!array[@]}|grep -q ^51$ && echo 51 exist
51 exist

$ printf "%s\n" ${!array[@]}|grep -q ^52$ && echo 52 exist

(give no answer)

And for associative array, you could use the same:

$ unset array
$ declare -A array
$ array["key1"]="red"
$ array["key2"]="black"
$ array["key3"]="blue"
$ echo ${array[@]}
blue black red

$ echo ${!array[@]}
key3 key2 key1

$ echo ${#array[@]}
3

$ set | grep ^array=
array=([key3]="blue" [key2]="black" [key1]="red" )

$ printf "%s\n" ${!array[@]}|grep -q ^key2$ && echo key2 exist || echo key2 not exist
key2 exist

$ printf "%s\n" ${!array[@]}|grep -q ^key5$ && echo key5 exist || echo key5 not exist
key5 not exist

You could do the job without the need of externals tools (no printf|grep as pure bash), and why not, build checkIfExist() as a new bash function:

$ checkIfExist() {
    eval 'local keys=${!'$1'[@]}';
    eval "case '$2' in
        ${keys// /|}) return 0 ;;
        * ) return 1 ;;
      esac";
}

$ checkIfExist array key2 && echo exist || echo don\'t
exist

$ checkIfExist array key5 && echo exist || echo don\'t
don't

or even create a new getIfExist bash function that return the desired value and exit with false result-code if desired value not exist:

$ getIfExist() {
    eval 'local keys=${!'$1'[@]}';
    eval "case '$2' in
        ${keys// /|}) echo \${$1[$2]};return 0 ;;
        * ) return 1 ;;
      esac";
}

$ getIfExist array key1
red
$ echo $?
0

$ # now with an empty defined value
$ array["key4"]=""
$ getIfExist array key4

$ echo $?
0
$ getIfExist array key5
$ echo $?
1

Solution 4 - Arrays

What about a -n test and the :- operator?

For example, this script:

#!/usr/bin/env bash

set -e
set -u

declare -A sample

sample["ABC"]=2
sample["DEF"]=3

if [[ -n "${sample['ABC']:-}" ]]; then
  echo "ABC is set"
fi

if [[ -n "${sample['DEF']:-}" ]]; then
  echo "DEF is set"
fi

if [[ -n "${sample['GHI']:-}" ]]; then
  echo "GHI is set"
fi

Prints:

ABC is set
DEF is set

Solution 5 - Arrays

tested in bash 4.3.39(1)-release

declare -A fmap
fmap['foo']="boo"

key='foo'
# should echo foo is set to 'boo'
if [[ -z "${fmap[${key}]}" ]]; then echo "$key is unset in fmap"; else echo "${key} is set to '${fmap[${key}]}'"; fi
key='blah'
# should echo blah is unset in fmap
if [[ -z "${fmap[${key}]}" ]]; then echo "$key is unset in fmap"; else echo "${key} is set to '${fmap[${key}]}'"; fi

Solution 6 - Arrays

Reiterating this from Thamme:

[[ ${array[key]+Y} ]] && echo Y || echo N

This tests if the variable/array element exists, including if it is set to a null value. This works with a wider range of bash versions than -v and doesn't appear sensitive to things like set -u. If you see a "bad array subscript" using this method please post an example.

Solution 7 - Arrays

This is the easiest way I found for scripts.

<search> is the string you want to find, ASSOC_ARRAY the name of the variable holding your associative array.

Dependign on what you want to achieve:

key exists:

if grep -qe "<search>" <(echo "${!ASSOC_ARRAY[@]}"); then echo key is present; fi

key exists not:

if ! grep -qe "<search>" <(echo "${!ASSOC_ARRAY[@]}"); then echo key not present; fi

value exists:

if grep -qe "<search>" <(echo "${ASSOC_ARRAY[@]}"); then echo value is present; fi

value exists not:

if ! grep -qe "<search>" <(echo "${ASSOC_ARRAY[@]}"); then echo value not present; fi

Solution 8 - Arrays

I wrote a function to check if a key exists in an array in Bash:

# Check if array key exists
# Usage: array_key_exists $array_name $key
# Returns: 0 = key exists, 1 = key does NOT exist
function array_key_exists() {
    local _array_name="$1"
    local _key="$2"
    local _cmd='echo ${!'$_array_name'[@]}'
    local _array_keys=($(eval $_cmd))
    local _key_exists=$(echo " ${_array_keys[@]} " | grep " $_key " &>/dev/null; echo $?)
    [[ "$_key_exists" = "0" ]] && return 0 || return 1
}

Example

declare -A my_array
my_array['foo']="bar"

if [[ "$(array_key_exists 'my_array' 'foo'; echo $?)" = "0" ]]; then
    echo "OK"
else
    echo "ERROR"
fi

Tested with GNU bash, version 4.1.5(1)-release (i486-pc-linux-gnu)

Solution 9 - Arrays

For all time people, once and for all.

There's a "clean code" long way, and there is a shorter, more concise, bash centered way.

$1 = The index or key you are looking for.

$2 = The array / map passed in by reference.

function hasKey ()
{
    local -r needle="${1:?}"
    local -nr haystack=${2:?}

    for key in "${!haystack[@]}"; do
        if [[ $key == $needle ]] ;
            return 0
        fi
    done

    return 1
}

A linear search can be replaced by a binary search, which would perform better with larger data sets. Simply count and sort the keys first, then do a classic binary halving of of the haystack as you get closer and closer to the answer.

Now, for the purist out there that is like "No, I want the more performant version because I may have to deal with large arrays in bash," lets look at a more bash centered solution, but one that maintains clean code and the flexibility to deal with arrays or maps.

function hasKey ()
{
    local -r needle="${1:?}"
    local -nr haystack=${2:?}

    [ -n ${haystack["$needle"]+found} ]
}

The line [ -n ${haystack["$needle"]+found} ]uses the ${parameter+word} form of bash variable expansion, not the ${parameter:+word} form, which attempts to test the value of a key, too, which is not the matter at hand.

Usage

local -A person=(firstname Anthony lastname Rutledge)

if hasMapKey "firstname" person; then
     # Do something
fi

> When not performing substring expansion, using the form described > below (e.g., ‘:-’), Bash tests for a parameter that is unset or null. > Omitting the colon results in a test only for a parameter that is > unset. Put another way, if the colon is included, the operator tests > for both parameter’s existence and that its value is not null; if the > colon is omitted, the operator tests only for existence. > > ${parameter:-word} > > If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted. > ${parameter:=word} > > If parameter is unset or null, the expansion of word is assigned to parameter. The value of parameter is then substituted. Positional > parameters and special parameters may not be assigned to in this way. > ${parameter:?word} > > If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard > error and the shell, if it is not interactive, exits. Otherwise, the > value of parameter is substituted. ${parameter:+word} > > If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.

https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Parameter-Expansion

If $needle does not exist expand to nothing, otherwise expand to the non-zero length string, "found". This will make the -n test succeed if the $needle in fact does exist (as I say "found"), and fail otherwise.

Solution 10 - Arrays

I get bad array subscript error when the key I'm checking is not set. So, I wrote a function that loops over the keys:

#!/usr/bin/env bash
declare -A helpList 

function get_help(){
    target="$1"

    for key in "${!helpList[@]}";do
        if [[ "$key" == "$target" ]];then
            echo "${helpList["$target"]}"
            return;
        fi
    done
}

targetValue="$(get_help command_name)"
if [[ -z "$targetvalue" ]];then
    echo "command_name is not set"
fi

It echos the value when it is found & echos nothing when not found. All the other solutions I tried gave me that error.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLuca BorrioneView Question on Stackoverflow
Solution 1 - ArraysdoubleDownView Answer on Stackoverflow
Solution 2 - ArraysVineetView Answer on Stackoverflow
Solution 3 - ArraysF. HauriView Answer on Stackoverflow
Solution 4 - ArraysGuyPaddockView Answer on Stackoverflow
Solution 5 - ArraysgdoubleodView Answer on Stackoverflow
Solution 6 - ArraysLawrence KirbyView Answer on Stackoverflow
Solution 7 - ArrayssjasView Answer on Stackoverflow
Solution 8 - ArraysLucas StadView Answer on Stackoverflow
Solution 9 - ArraysAnthony RutledgeView Answer on Stackoverflow
Solution 10 - ArraysReedView Answer on Stackoverflow