Get the index of a value in a Bash array

ArraysBashIndexing

Arrays Problem Overview


I have something in bash like

myArray=('red' 'orange' 'green')

And I would like to do something like

echo ${myArray['green']}

Which in this case would output 2. Is this achievable?

Arrays Solutions


Solution 1 - Arrays

This will do it:

#!/bin/bash

my_array=(red orange green)
value='green'

for i in "${!my_array[@]}"; do
   if [[ "${my_array[$i]}" = "${value}" ]]; then
       echo "${i}";
   fi
done

Obviously, if you turn this into a function (e.g. get_index() ) - you can make it generic

Solution 2 - Arrays

You must declare your array before use with

declare -A myArray
myArray=([red]=1 [orange]=2 [green]=3)
echo ${myArray['orange']}

Solution 3 - Arrays

There is also one tricky way:

echo ${myArray[@]/green//} | cut -d/ -f1 | wc -w | tr -d ' '

And you get 2 Here are references

Solution 4 - Arrays

No. You can only index a simple array with an integer in bash. Associative arrays (introduced in bash 4) can be indexed by strings. They don't, however, provided for the type of reverse lookup you are asking for, without a specially constructed associative array.

$ declare -A myArray
$ myArray=([red]=0 [orange]=1 [green]=2)
$ echo ${myArray[green]}
2

Solution 5 - Arrays

A little more concise and works in Bash 3.x:

my_array=(red orange green)
value='green'
 
for i in "${!my_array[@]}"; do
   [[ "${my_array[$i]}" = "${value}" ]] && break
done

echo $i

Solution 6 - Arrays

Another tricky one-liner:

index=$((-1 + 10#0$(IFS=$'\n' echo "${my_array[*]}" | grep --line-number --fixed-strings -- "$value" | cut -f1 -d:)))

features:

  • supports elements with spaces
  • returns -1 when not found

caveats:

  • requires value to be non-empty
  • difficult to read

Explanations by breaking it down in execution order:

IFS=$'\n' echo "${my_array[*]}"

set array expansion separator (IFS) to a new line char & expand the array

grep --line-number --fixed-strings -- "$value"

grep for a match:

  • show line numbers (--line-number or -n)

  • use a fixed string (--fixed-strings or -F; disables regex)

  • allow for elements starting with a - (--)

    cut -f1 -d:

extract only the line number (format is <line_num>:<matched line>)

$((-1 + 10#0$(...)))

subtract 1 since line numbers are 1-indexed and arrays are 0-indexed

  • if $(...) does not match:

    • nothing is returned & the default of 0 is used (10#0)
  • if $(...) matches:

    • a line number exists & is prefixed with 10#0; i.e. 10#02, 10#09, 10#014, etc
    • the 10# prefix forces base-10/decimal numbers instead of octal


Using awk instead of grep, cut & bash arithmetic:

IFS=$'\n'; awk "\$0 == \"${value//\"/\\\"}\" {print NR-1}" <<< "${my_array[*]}"

features:

  • supports elements with spaces
  • supports empty elements
  • less commands opened in a subshell

caveats:

  • returns when not found

Explanations by breaking it down in execution order:

IFS=$'\n' [...] <<< "${my_array[*]}"

set array expansion separator (IFS) to a new line char & expand the array

awk "\$0 == \"${value//\"/\\\"}\" {print NR-1}"

match the entire line & print the 0-indexed line number

  • ${value//\"/\\\"} replaces double quotes in $value with escaped versions
  • since we need variable substitution, this segment has more escaping than wanted

Solution 7 - Arrays

This might just work for arrays,

my_array=(red orange green)
echo "$(printf "%s\n" "${my_array[@]}")" | grep -n '^orange$' | sed 's/:orange//'

Output:

2

If you want to find header index in a tsv file,

head -n 1 tsv_filename | sed 's/\t/\n/g' | grep -n '^header_name$' | sed 's/:header_name//g'

Solution 8 - Arrays

I like that solution:

let "n=(`echo ${myArray[@]} | tr -s " " "\n" | grep -n "green" | cut -d":" -f 1`)-1"

The variable n will contain the result!

Solution 9 - Arrays

This is just another way to initialize an associative array as chepner showed. Don't forget that you need to explicitly declare or typset an associative array with -A attribute.

i=0; declare -A myArray=( [red]=$((i++)) [orange]=$((i++)) [green]=$((i++)) )
echo ${myArray[green]}
2

This removes the need to hard code values and makes it unlikely you will end up with duplicates.

If you have lots of values to add it may help to put them on separate lines.

i=0; declare -A myArray; 
myArray+=( [red]=$((i++)) )
myArray+=( [orange]=$((i++)) )
myArray+=( [green]=$((i++)) )
echo ${myArray[green]}
2

Say you want an array of numbers and lowercase letters (eg: for a menu selection) you can also do something like this.

declare -a mKeys_1=( {{0..9},{a..z}} );
i=0; declare -A mKeys_1_Lookup; eval mKeys_1_Lookup[{{0..9},{a..z}}]="$((i++))";

If you then run

echo "${mKeys_1[15]}"
f
echo "${mKeys_1_Lookup[f]}"
15

Solution 10 - Arrays

In zsh you can do

xs=( foo bar qux )
echo ${xs[(ie)bar]}

see zshparam(1) subsection Subscript Flags

Solution 11 - Arrays

This outputs the 0-based array index of the query (here "orange").

echo $(( $(printf "%s\n" "${myArray[@]}" | sed -n '/^orange$/{=;q}') - 1 ))

If the query does not occur in the array then the above outputs -1.

If the query occurs multiple times in the array then the above outputs the index of the query's first occurrence.

Since this solution invokes sed, I doubt that it can compete with some of the pure bash solutions in this thread in efficiency.

Solution 12 - Arrays

This shows some methods for returning an index of an array member. The array uses non-applicable values for the first and last index, to provide an index starting at 1, and to provide limits.

The while loop is an interesting method for iteration, with cutoff, with the purpose of generating an index for an array value, the body of the loop contains only a colon for null operation. The important part is the iteration of i until a match, or past the possible matches.

The function indexof() will translate a text value to an index. If a value is unmatched the function returns an error code that can be used in a test to perform error handling. An input value unmatched to the array will exceed the range limits (-gt, -lt) tests.

There is a test (main code) that loops good/bad values, the first 3 lines are commented out, but try some variations to see interesting results (lines 1,3 or 2,3 or 4). I included some code that considers error conditions, because it can be useful.

The last line of code invokes function indexof with a known good value "green" which will echo the index value.

indexof(){
  local s i;

  #   0    1   2     3    4
  s=( @@@ red green blue @o@ )

  while [ ${s[i++]} != $1 ] && [ $i -lt ${#s[@]} ]; do :; done

  [ $i -gt 1 ] && [ $i -lt ${#s[@]} ] || return

  let i--

  echo $i
};# end function indexof

# --- main code ---
echo -e \\033c
echo 'Testing good and bad variables:'
for x in @@@ red pot green blue frog bob @o@;
do
  #v=$(indexof $x) || break
  #v=$(indexof $x) || continue
  #echo $v
  v=$(indexof $x) && echo -e "$x:\t ok" || echo -e "$x:\t unmatched"
done 

echo -e '\nShow the index of array member green:'
indexof green

Solution 13 - Arrays

myArray=('red' 'orange' 'green')
echo ${myArray[@]}
arrayElementToBeRemoved='orange'
echo "removing element: $arrayElementToBeRemoved"
# Find index of the array element (to be kept or preserved)
let "index=(`echo ${myArray[@]} | tr -s " " "\n" | grep -n "$arrayElementToBeRemoved" | cut -d":" -f 1`)-1"
unset "myArray[$index]"
echo ${myArray[@]}

Solution 14 - Arrays

I wanted something similar myself and avoiding a loop, came up with ...

myArray=('red' 'orange' 'green')
declare -p myArray | sed -n "s,.*\[\([^]]*\)\]=\"green\".*,\1,p"

... which leaves stdout unsullied should the element not be found...

$ myArray=('red' 'orange' 'green')
$ declare -p myArray | sed -n "s,.*\[\([^]]*\)\]=\"green\".*,\1,p"
2

$ declare -p myArray | sed -n "s,.*\[\([^]]*\)\]=\"gren\".*,\1,p"
$

After which I googled, found this question and thought I'd share ;)

Solution 15 - Arrays

This one outputs the 1based NEUROMANCER index of the character "Molly" ;)

get_index() {

  declare -n dummy_array="$1"
  # alternative: split read -ra array <<< "${dummy_array[@]}"
  local array=( "${dummy_array[@]}" )
  # alternative: local value; value="$( for dummy_value; do true; done; echo "$dummy_value" )"
  local value=$2
  local length="${#array[@]}"
  local i=0
  
  while (( i < length ))
  do
    if [ "${array[$i]}" = "$value" ]
    then echo $(( i + 1 )); return 0
    fi; (( i++ ))
  done
  
  echo "$2 not found beneath $1"
  exit 1

}

NEUROMANCER=(Case Molly Riviera)
get_index NEUROMANCER Molly
get_index NEUROMANCER 'John Doe'

If you then run:

$ bash script.sh
2
John Doe not found beneath NEUROMANCER

Solution 16 - Arrays

function array_indexof() {
  [ $# -lt 2 ] && return 1
  local a=("$@")
  local v="${a[-1]}"
  unset a[-1]
  local i
  for i in ${!a[@]}; do
    if [ "${a[$i]}" = "$v" ]; then
      echo $i
      return 0 # stop after first match
    fi
  done
  return 1
}

a=(a b c d)
i=$(array_indexof "${a[@]}" d)
echo $i # 3

Solution 17 - Arrays

Purest bash function:

_indexof() {
        for ((;$#;)) ; do
                case "$1" in
                        --) shift ; break ;;
                        -*) printf "Usage: %s [--] VALUE ARRAY...\n" "$FUNCNAME" >&2 ; return 2 ;;
                        *) break ;;
                esac
                shift
        done
        local asize value=$1
        shift
        asize=$#
        ((asize)) || { printf "Usage: %s [--] VALUE ARRAY...\n" "$FUNCNAME" >&2 ; return 2 ;}
        while (($#)) ; do
                [[ "$1" != "${value}" ]] || break
                shift
        done
        (($#)) || return 1
        echo $((asize-$#))
}
  • ✓ work with any inputs
  • ✓ work even with "set -e"
  • ✓ integrate helping error message
  • ✓ return non-zero on error (1 if not found, 2 if non-proper call)
  • ✓ output first index if found

Example:

set "Peace & Love" "ПТН Х̆ЛО" "Cupidity" "Vanity" "$(printf "Ideology\nFear")" "Bayraktar"
_indexof "Vanity" "$@"

Return 0, output "3".

Solution 18 - Arrays

Simple solution:

my_array=(red orange green)
echo ${my_array[*]} | tr ' ' '\n' | awk '/green/ {print NR-1}'

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
Questionuser137369View Question on Stackoverflow
Solution 1 - ArraysSteve WalshView Answer on Stackoverflow
Solution 2 - ArraysOlaf DietscheView Answer on Stackoverflow
Solution 3 - ArraysPiotrOView Answer on Stackoverflow
Solution 4 - ArrayschepnerView Answer on Stackoverflow
Solution 5 - ArrayscmcgintyView Answer on Stackoverflow
Solution 6 - ArrayssrbsView Answer on Stackoverflow
Solution 7 - ArraysManish SharmaView Answer on Stackoverflow
Solution 8 - Arraysuser3680055View Answer on Stackoverflow
Solution 9 - ArrayssbtsView Answer on Stackoverflow
Solution 10 - ArraysJan MatějkaView Answer on Stackoverflow
Solution 11 - ArraysCarl SmithView Answer on Stackoverflow
Solution 12 - ArrayscognativeorcView Answer on Stackoverflow
Solution 13 - Arrayskp123View Answer on Stackoverflow
Solution 14 - Arrayspointo1dView Answer on Stackoverflow
Solution 15 - ArraysAIeView Answer on Stackoverflow
Solution 16 - ArraysMila NautikusView Answer on Stackoverflow
Solution 17 - ArraysJbarView Answer on Stackoverflow
Solution 18 - ArraysNikhil GuptaView Answer on Stackoverflow