Get the index of a value in a Bash array

喜夏-厌秋 提交于 2020-06-09 07:30:09

问题


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?


回答1:


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




回答2:


You must declare your array before use with

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



回答3:


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



回答4:


There is also one tricky way:

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

And you get 2 Here are references




回答5:


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



回答6:


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!




回答7:


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



回答8:


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



回答9:


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'



回答10:


In zsh you can do

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

see zshparam(1) subsection Subscript Flags




回答11:


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.




回答12:


Simple solution:

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


来源:https://stackoverflow.com/questions/15028567/get-the-index-of-a-value-in-a-bash-array

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!