web-dev-qa-db-fra.com

Obtenir l'index d'une valeur dans un tableau de Bash

J'ai quelque chose dans bash comme

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

Et je voudrais faire quelque chose comme

echo ${myArray['green']}

Ce qui dans ce cas produirait 2. Est-ce réalisable?

39
user137369

Cela va le faire:

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

Évidemment, si vous convertissez cela en une fonction (par exemple, get_index ()), vous pouvez le rendre générique

60
Steve Walsh

Vous devez déclarer votre tableau avant utilisation avec 

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

Non. Vous pouvez uniquement indexer un tableau simple avec un entier dans bash. Les tableaux associatifs (introduits dans bash 4) peuvent être indexés par des chaînes. Cependant, ils ne fournissent pas le type de recherche inversée que vous demandez, sans tableau associatif spécialement construit.

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

Il y a aussi un moyen astucieux:

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

Et vous obtenez 2 Voici des références

9
PiotrO

J'aime cette solution:

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

La variable n contiendra le résultat!

2
user3680055

C’est juste une autre façon d’initialiser un tableau associatif comme le montre chepner . N'oubliez pas que vous devez explicitement declare ou typset un tableau associatif avec l'attribut -A.

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

Cela supprime le besoin de coder en dur les valeurs et rend peu probable que vous obteniez des doublons.

Si vous avez beaucoup de valeurs à ajouter, cela peut aider de les mettre sur des lignes séparées.

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

Supposons que vous vouliez un tableau de chiffres et de lettres minuscules (par exemple: pour une sélection de menu), vous pouvez également faire quelque chose comme ceci.

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

Si vous courez alors

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

Cela pourrait fonctionner pour les tableaux,

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

Sortie:

2

Si vous voulez trouver un index d’en-tête dans un fichier tsv,

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

Un autre one-liner délicat:

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

fonctionnalités:

  • prend en charge les éléments avec des espaces
  • renvoie -1 si non trouvé

mises en garde:

  • nécessite que value soit non vide
  • difficile à lire

Explications en le décomposant en ordre d'exécution:

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

définir le séparateur d'expansion de tableau (IFS) sur une nouvelle ligne char & développer le tableau

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

grep pour un match:

  • affiche les numéros de ligne (--line-number ou -n)
  • utilisez une chaîne fixe (--fixed-strings ou -F; désactive regex)
  • autoriser les éléments commençant par - (--)

    cut -f1 -d:

extraire uniquement le numéro de ligne (le format est <line_num>:<matched line>)

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

soustrayez 1 puisque les numéros de ligne sont indexés 1 et les tableaux indexés 0

  • si $(...) ne correspond pas:

    • rien n'est renvoyé et la valeur par défaut de 0 est utilisée (10#0)
  • si $(...) correspond à:
    • un numéro de ligne existe et est préfixé par 10#0; i.e. 10#02, 10#09, 10#014, etc.
    • le préfixe 10# force les nombres base-10/décimaux au lieu d'octal


Utilisation de awk au lieu de grep, cut & bash arithmetic:

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

fonctionnalités:

  • prend en charge les éléments avec des espaces
  • supporte les éléments vides
  • moins de commandes ouvertes dans un sous-shell

mises en garde:

  • renvoie si non trouvé

Explications en le décomposant en ordre d'exécution:

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

définir le séparateur d'expansion de tableau (IFS) sur une nouvelle ligne char & développer le tableau

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

faire correspondre la ligne entière et imprimer le numéro de ligne indexé 0

  • ${value//\"/\\\"} remplace les guillemets dans $value par les versions échappées
  • puisque nous avons besoin de la substitution de variable, ce segment a plus échappé que voulu
1
srbs

Un peu plus concis et fonctionne dans 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

En zsh tu peux faire

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

voir zshparam (1) sous-section Indicateurs sur l'indice

0
yaccz

Solution simple:

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