Boucle sur les tableaux, impression de l'index et de la valeur

197

Je veux faire quelque chose comme ça:

foo=( )
foo[0]="bar"
foo[35]="baz"
for((i=0;i<${#foo[@]};i++))
do
    echo "$i: ${foo[$i]}"
done
# Output:
# 0: bar
# 1: 

Ensuite, j'ai essayé de le parcourir en utilisant for in:

foo=( )
foo[0]="bar"
foo[35]="baz"
for i in ${foo[@]}
do
    echo "?: $i"
done
# Output:
# ?: bar
# ?: naz

mais ici je ne connais pas la valeur de l'indice.

Je sais que tu pourrais quelque chose comme

foo=( )
foo[0]="bar"
foo[35]="baz"
declare -p foo
# Output:
# declare -a foo='([0]="bar" [35]="baz")'

mais, ne pouvez-vous pas le faire d'une autre manière?

Tyilo
la source

Réponses:

331

Vous trouverez les clés du tableau avec "${!foo[@]}"( référence ), donc:

for i in "${!foo[@]}"; do 
  printf "%s\t%s\n" "$i" "${foo[$i]}"
done

Ce qui signifie que les index seront là $itandis que les éléments eux-mêmes doivent être accessibles via${foo[$i]}

Glenn Jackman
la source
6
Remarque importante, bien qu'elle soit itérable, une liste de mots séparés par des espaces n'est pas un tableau. Enveloppez-le comme ceci (a b c)pour le convertir en tableau.
Breedly
4
L'utilisation de [@]et de guillemets doubles signifie qu'il ne s'agit pas d'une "liste de mots séparés par des espaces". Vous obtenez la liste des clés de tableau réelles, même si les clés individuelles contiennent des espaces.
glenn jackman
@glennjackman pouvez-vous expliquer cela plusThe use of [@] and double quotes means it's not a "space separated list of words"
Kasun Siyambalapitiya
1
"${foo[@]}"prend la variable (tableau) fooet l'étend comme un tableau, en conservant l'identité de ses éléments, c'est-à-dire en ne les divisant pas sur des espaces. Si foo=(x 'y z'), alors f "${foo[@]}"appelle favec deux arguments, xet 'y z'. Les requêtes de métadonnées aiment "${!foo[@]}"et "${#foo[@]}"agissent de la même manière foocomme un tableau.
BallpointBen
Bonne réponse, mais cette référence est impénétrable. Cette réponse explique simplement: " "${!foo[@]}"est la liste de tous les index définis dans le tableau".
pjhsea
22

vous pouvez toujours utiliser le paramètre d'itération:

ITER=0
for I in ${FOO[@]}
do  
    echo ${I} ${ITER}
    ITER=$(expr $ITER + 1)
done
Eyal Ch
la source
7
((ITER++))in modern bash
David Tonhofer
Pourquoi post-incrémentation? Vous ne voulez que la valeur incrémentée, donc ((++ ITER)) est plus directement une déclaration de ce que vous voulez faire ....
MikeW
4
Non, pas "TOUJOURS". Un hachage peut avoir des «trous», ce qui signifie que tous les nombres ne sont pas des index. Dans votre exemple, $ {ITER} n'est pas toujours l'index de $ {I}.
Marco le
10
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done
Aruy Aruy
la source
Votre réponse mérite certainement une petite explication. Veuillez vous référer à stackoverflow.com/help/how-to-answer . Les commentaires aideraient à créer du contenu interrogeable.
J. Chomel
3
Bien que cet extrait de code puisse résoudre la question, inclure une explication contribue vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code. Essayez également de ne pas surcharger votre code avec des commentaires explicatifs, cela réduit la lisibilité du code et des explications!
kayess
J'ai aimé la concision de la réponse et de la stratégie. Pour ceux qui se demandent c'est bash et la stratégie est que vous gardez simplement une trace de l'index du tableau par vous-même.
Alexander D'Attore
5

Dans bash 4, vous pouvez utiliser des tableaux associatifs:

declare -A foo
foo[0]="bar"
foo[35]="baz"
for key in "${!foo[@]}"
do
    echo "key: $key, value: ${foo[$key]}"
done

# output
# $ key: 0, value bar.
# $ key: 35, value baz.

Dans bash 3, cela fonctionne (fonctionne également dans zsh):

map=( )
map+=("0:bar")
map+=("35:baz")

for keyvalue in "${map[@]}" ; do
    key=${keyvalue%%:*}
    value=${keyvalue#*:}
    echo "key: $key, value $value."
done
mattmc3
la source
2

Astuce simple en une ligne pour vider un tableau

J'ai ajouté une valeur avec les espaces:

foo=()
foo[12]="bar"
foo[42]="foo bar baz"
foo[35]="baz"

Moi, pour vider rapidement tableaux ou tableaux associatifs que j'utilise

Cette commande en une ligne:

paste <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")

Rendra:

12  bar
35  baz
42  foo bar baz

Expliqué

  • printf "%s\n" "${!foo[@]}"imprimera toutes les clés séparées par une nouvelle ligne ,
  • printf "%s\n" "${foo[@]}"affichera toutes les valeurs séparées par une nouvelle ligne ,
  • paste <(cmd1) <(cmd2)fusionnera la sortie de cmd1et cmd2ligne par ligne.

Tunning

Cela pourrait être réglé par le -dcommutateur:

paste -d : <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")
12:bar
35:baz
42:foo bar baz

ou même:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")
foo[12]='bar'
foo[35]='baz'
foo[42]='foo bar baz'

Le tableau associatif fonctionnera de la même manière:

declare -A bar=([foo]=snoopy [bar]=nice [baz]=cool [foo bar]='Hello world!')
paste -d = <(printf "bar[%s]\n" "${!bar[@]}") <(printf '"%s"\n' "${bar[@]}")
bar[foo bar]="Hello world!"
bar[foo]="snoopy"
bar[bar]="nice"
bar[baz]="cool"

Problème avec les sauts de ligne ou les caractères spéciaux

Malheureusement, il y a au moins une condition qui empêche cela de fonctionner: lorsque la variable contient une nouvelle ligne:

foo[17]=$'There is one\nnewline'

La commande pastefusionnera ligne par ligne, donc la sortie deviendra incorrecte:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")
foo[12]='bar'
foo[17]='There is one
foo[35]=newline'
foo[42]='baz'
='foo bar baz'

Pour ce travail, vous pouvez utiliser à la %qplace de %sdans la deuxième printfcommande (et whipe quote):

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "%q\n" "${foo[@]}")

Rendra parfait:

foo[12]=bar
foo[17]=$'There is one\nnewline'
foo[35]=baz
foo[42]=foo\ bar\ baz

De man bash:

          %q     causes  printf  to output the corresponding argument in a
                 format that can be reused as shell input.
F. Hauri
la source
Mon vote pour la printf "%q\n" "${var[@]}" nouvelle ligne était mon problème!
techno le
1
users=("kamal" "jamal" "rahim" "karim" "sadia")
index=()
t=-1

for i in ${users[@]}; do
  t=$(( t + 1 ))
  if [ $t -eq 0 ]; then
    for j in ${!users[@]}; do
      index[$j]=$j
    done
  fi
  echo "${index[$t]} is $i"
done
code4mk
la source
2
C'est faux! La boucle intérieure est inutile et pourrait conduire à de mauvais résultats!
F.Hauri