问题
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
-1when not found
caveats:
- requires
valueto 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-numberor-n) - use a fixed string (
--fixed-stringsor-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
0is used (10#0)
- nothing is returned & the default of
- 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
- a line number exists & is prefixed with
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$valuewith 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