Fazendo loop em matrizes, imprimindo índice e valor

184

Eu quero fazer algo assim:

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

Então eu tentei fazer um loop através dele usando for:

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

mas aqui não sei o valor do índice.

Eu sei que você poderia algo como

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

mas você não pode fazer isso de outra maneira?

Tyilo
fonte

Respostas:

317

Você encontraria as chaves da matriz com "${!foo[@]}"( referência ), portanto:

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

O que significa que os índices aparecerão $ienquanto os próprios elementos deverão ser acessados ​​via${foo[$i]}

Glenn Jackman
fonte
6
Nota importante, embora iterável, uma lista de palavras separadas por espaço não é uma matriz. Embrulhe-o assim (a b c)para convertê-lo em uma matriz.
Breedly
4
O uso de [@]e aspas duplas significa que não é uma "lista de palavras separadas por espaço". Você obtém a lista de chaves de matriz reais, mesmo que as chaves individuais contenham espaços em branco.
precisa saber é o seguinte
@glennjackman você pode explicar isso maisThe use of [@] and double quotes means it's not a "space separated list of words"
Kasun Siyambalapitiya
1
"${foo[@]}"pega a variável (array) fooe a expande como uma matriz, mantendo a identidade de seus elementos, ou seja, não os dividindo em espaços em branco. Se foo=(x 'y z'), então f "${foo[@]}"chama fcom dois argumentos, xe 'y z'. Consultas de metadados como "${!foo[@]}"e "${#foo[@]}"agem da mesma forma foocomo uma matriz.
usar o seguinte texto
Resposta certa, mas essa referência é inescrutável. Esta resposta é útil apenas explica: " "${!foo[@]}"é a lista de todos os índices definidos na matriz".
pjhsea
17

você sempre pode usar o parâmetro de iteração:

ITER=0
for I in ${FOO[@]}
do  
    echo ${I} ${ITER}
    ITER=$(expr $ITER + 1)
done
Eyal Ch
fonte
5
((ITER++))no bash moderno
David Tonhofer 18/01/18
Por que pós-incremento? Você só quer o valor incrementado, portanto, ((++ ITER)) é mais diretamente uma declaração de que você quer fazer ....
MikeW
3
Não, não "SEMPRE". Um hash pode ter "buracos", o que significa que nem todos os números são um índice. No seu exemplo, o $ {ITER} nem sempre é o índice de $ {I}.
Marco
10
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done
Aruy Aruy
fonte
Sua resposta certamente vale uma pequena explicação. Por favor, consulte stackoverflow.com/help/how-to-answer . Os comentários ajudariam a criar conteúdo pesquisável.
J.J. Chomel
3
Embora esse trecho de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código. Tente também não sobrecarregar seu código com comentários explicativos, pois isso reduz a legibilidade do código e das explicações!
kayess
2

No bash 4, você pode usar matrizes associativas:

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.

No bash 3, isso funciona (também funciona no zsh):

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

for keyvalue in "${map[@]}" ; do
    key=${keyvalue%%:*}
    value=${keyvalue#*:}
    echo "key: $key, value $value."
done
mattmc3
fonte
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
fonte
1
Isto está errado! O loop interno é inútil e pode gerar resultados errados!
F. Hauri
1

Truque simples de uma linha para descarregar array

Eu adicionei um valor com espaços:

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

Eu, para despejar rapidamente matrizes ou matrizes associativas que eu uso

Este comando de uma linha:

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

Renderizará:

12  bar
35  baz
42  foo bar baz

Explicado

  • printf "%s\n" "${!foo[@]}"imprimirá todas as chaves separadas por uma nova linha ,
  • printf "%s\n" "${foo[@]}"imprimirá todos os valores separados por uma nova linha ,
  • paste <(cmd1) <(cmd2)irá mesclar a saída de cmd1e cmd2linha por linha.

Tunning

Isso pode ser ajustado pelo -dswitch:

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

ou até:

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

A matriz associativa funcionará da mesma maneira:

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 com novas linhas ou caracteres especiais

Infelizmente, há pelo menos uma condição para que isso não funcione mais: quando a variável contém nova linha:

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

O comando pastemesclará linha por linha, portanto, a saída ficará errada:

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 trabalho, você pode usar em %qvez do %ssegundo printfcomando (e aspas):

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

Tornará perfeito:

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
fonte
Meu voto printf "%q\n" "${var[@]}" positivo para newline foi meu problema!
techno