Recorriendo las matrices, imprimiendo tanto el índice como el valor

184

Quiero hacer algo como esto:

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

Luego intenté recorrerlo usando for in:

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

pero aquí no sé el valor del índice.

Sé que podrías hacer algo como

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

pero, ¿no puedes hacerlo de otra manera?

Tyilo
fuente

Respuestas:

317

Encontraría las claves de matriz con "${!foo[@]}"( referencia ), entonces:

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

Lo que significa que los índices estarán disponibles, $imientras que los elementos en sí deben ser accedidos a través de${foo[$i]}

Glenn Jackman
fuente
66
Nota importante, aunque iterable, una lista de palabras separadas por espacios no es una matriz. Envuélvelo así (a b c)para convertirlo en una matriz.
Breedly
44
El uso de [@]comillas dobles significa que no es una "lista de palabras separadas por espacios". Obtiene la lista de claves de matriz reales, incluso si las claves individuales contienen espacios en blanco.
Glenn Jackman
@glennjackman, ¿puedes explicar esto másThe use of [@] and double quotes means it's not a "space separated list of words"
Kasun Siyambalapitiya, el
1
"${foo[@]}"toma la variable (matriz) fooy la expande como una matriz, manteniendo la identidad de sus elementos, es decir, sin dividirlos en espacios en blanco. Si foo=(x 'y z'), entonces f "${foo[@]}"llama fcon dos argumentos, xy 'y z'. Las consultas de metadatos tienen gusto "${!foo[@]}"y "${#foo[@]}"actúan de manera similar foocomo una matriz.
BallpointBen
Respuesta correcta, pero esa referencia es inescrutable. Esta respuesta útilmente explica: " "${!foo[@]}"es la lista de todos los índices establecidos en la matriz".
pjhsea
18

siempre puedes usar el parámetro de iteración:

ITER=0
for I in ${FOO[@]}
do  
    echo ${I} ${ITER}
    ITER=$(expr $ITER + 1)
done
Eyal Ch
fuente
55
((ITER++))en fiesta moderna
David Tonhofer
¿Por qué post-incremento? Solo desea que se incremente el valor, por lo tanto ((++ ITER)) es más directamente una declaración de lo que quiere que se haga ...
MikeW
3
No, no "SIEMPRE". Un hash puede tener "agujeros", lo que significa que no todos los números son un índice. En su ejemplo, $ {ITER} no siempre es el índice de $ {I}.
Marco
10
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done
Aruy Aruy
fuente
Su respuesta ciertamente vale una pequeña explicación. Por favor, consulte stackoverflow.com/help/how-to-answer . Los comentarios ayudarían a crear contenido de búsqueda.
J. Chomel
3
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código. Por favor, también trate de no saturar su código con comentarios explicativos, ¡esto reduce la legibilidad tanto del código como de las explicaciones!
kayess 05 de
2

En bash 4, puede usar matrices asociativas:

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.

En bash 3, esto funciona (también funciona en zsh):

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

for keyvalue in "${map[@]}" ; do
    key=${keyvalue%%:*}
    value=${keyvalue#*:}
    echo "key: $key, value $value."
done
mattmc3
fuente
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
fuente
1
¡Esto está mal! ¡El bucle interno es inútil y podría conducir a resultados incorrectos!
F. Hauri
1

Truco simple de una línea para volcar la matriz

He agregado un valor con espacios:

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

Yo, para volcar rápidamente matrices o matrices asociativas que uso

Este comando de una línea:

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

Rendirá:

12  bar
35  baz
42  foo bar baz

Explicado

  • printf "%s\n" "${!foo[@]}"imprimirá todas las teclas separadas por una nueva línea ,
  • printf "%s\n" "${foo[@]}"imprimirá todos los valores separados por una nueva línea ,
  • paste <(cmd1) <(cmd2)fusionará la salida de cmd1y cmd2línea por línea.

Tunning

Esto podría ajustarse mediante el -dinterruptor:

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

o incluso:

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

La matriz asociativa funcionará igual:

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"

Problema con líneas nuevas o caracteres especiales

Desafortunadamente, hay al menos una condición que hace que esto ya no funcione: cuando la variable contiene nueva línea:

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

El comando pastese fusionará línea por línea, por lo que la salida se volverá incorrecta:

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'

Para este trabajo, puede usar en %qlugar del %ssegundo printfcomando (y comillas):

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

Se hará perfecto:

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
fuente
¡Mi voto printf "%q\n" "${var[@]}" positivo para nueva línea fue mi problema!
techno