ti-enxame.com

Obtém o índice de um valor em um array Bash

Eu tenho algo em bash como

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

E eu gostaria de fazer algo como

echo ${myArray['green']}

Que neste caso seria a saída 2. Isso é possível?

39
user137369

Isso fará isso:

#!/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

Obviamente, se você transformar isso em uma função (por exemplo, get_index ()) - você pode torná-lo genérico

60
Steve Walsh

Você deve declarar sua matriz antes de usar com 

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

Não. Você só pode indexar um array simples com um inteiro em bash. Matrizes associativas (introduzidas em bash 4) podem ser indexadas por strings. No entanto, eles não fornecem o tipo de pesquisa inversa que você está solicitando, sem um array associativo especialmente construído.

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

Há também um caminho complicado:

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

E você ganha 2 Aqui estão referências

9
PiotrO

Eu gosto dessa solução:

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

A variável n irá conter o resultado!

2
user3680055

Esta é apenas outra maneira de inicializar um array associativo como o chepner mostrou. Não se esqueça de que você precisa explicitamente declare ou digitar um array associativo com o atributo -A.

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

Isso elimina a necessidade de codificar os valores e torna improvável que você acabe com duplicatas.

Se você tiver muitos valores para adicionar, pode ser útil colocá-los em linhas separadas.

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

Digamos que você quer uma matriz de números e letras minúsculas (por exemplo: para uma seleção de menu) você também pode fazer algo parecido com isto.

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

Se você correr então

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

Isso pode funcionar apenas para matrizes,

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

Saída:

2

Se você quiser encontrar o índice de cabeçalho em um arquivo tsv,

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

Outro one-liner complicado:

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

características:

  • suporta elementos com espaços
  • retorna -1 quando não encontrado

ressalvas:

  • requer que value seja não-vazia
  • difícil de ler

Explicações dividindo-as em ordem de execução:

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

set array expansion expansion (IFS) para uma nova linha char & expande o array

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

grep para um jogo:

  • mostre números de linha (--line-number ou -n)
  • use uma string fixa (--fixed-strings ou -F; desativa regex)
  • permitir elementos começando com - (--)

    corte -f1-d:

extrair apenas o número da linha (o formato é <line_num>:<matched line>)

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

subtraia 1, pois os números de linha são indexados em 1 e os arrays são indexados em 0

  • se $(...) não corresponder:

    • nada é retornado e o padrão 0 é usado (10#0)
  • se $(...) corresponde a
    • existe um número de linha & é prefixado com 10#0; ou seja, 10#02, 10#09, 10#014, etc
    • o prefixo 10# força números base-10/decimal em vez de octal


Usando awk em vez de grep, cut & bash arithmetic:

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

características:

  • suporta elementos com espaços
  • suporta elementos vazios
  • menos comandos abertos em um subshell

ressalvas:

  • retorna quando não encontrado

Explicações dividindo-as em ordem de execução:

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

set array expansion expansion (IFS) para uma nova linha char & expande o array

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

combine a linha inteira e imprima o número da linha indexada em 0

  • ${value//\"/\\\"} substitui as aspas duplas em $value pelas versões com escape
  • já que precisamos de substituição de variável, esse segmento tem mais escape que o desejado
1
srbs

Um pouco mais conciso e funciona no Bash 3.x:

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

for i in "${!my_array[@]}"; do
   [[ "${my_array[$i]}" = "${value}" ]] && break
done

echo $i
0
cmcginty

Em zsh você pode fazer

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

veja o zshparam (1) subseção Flags Subscritas

0
yaccz

Solução simples:

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