Retorno indireto de todos os elementos em uma matriz

9

A página do manual Bash descreve o uso de ${!a}para retornar o conteúdo da variável cujo nome é o conteúdo de a(um nível de indireção).

Eu gostaria de saber como retornar todos os elementos em uma matriz usando isso, ou seja,

a=(one two three)
echo ${a[*]}

retorna

one two three

Eu gostaria de:

b=a
echo ${!b[*]}

para retornar o mesmo. Infelizmente, não, mas retorna 0.

Atualizar

Dadas as respostas, agora percebo que meu exemplo era muito simples, pois é claro, algo como:

b=("${a[@]}")

Alcançará exatamente o que eu disse que precisava.

Então, aqui está o que eu estava tentando fazer:

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST=LIST_$whichone
LIST=${!_LIST[*]}

Obviamente, a leitura cuidadosa da página de manual do Bash mostra que isso não funcionará conforme o esperado, porque a última linha simplesmente retorna os índices da "matriz" $_LIST(não uma matriz).

Em qualquer caso, o seguinte deve fazer o trabalho (conforme indicado):

LIST=($(eval echo \${$_LIST[*]}))

ou ... (a rota que segui, eventualmente):

LIST_lys="lys1 lys2"
...
LIST=(${!_LIST})

Supondo, é claro, que os elementos não contenham espaços em branco.

Eric Smith
fonte
Adicione [@]ao ponteiro _LIST="LIST_${whichone}[@]"e, em seguida, use LIST=("${!_LIST}")para copiar a matriz. É uma boa idéia usar nomes de variáveis ​​em minúsculas para evitar conflitos com variáveis ​​de ambiente (Todas as maiúsculas).
Isaac Isaac

Respostas:

6

Eu acho que o uso de referência indireta da variável bash deve ser tratado literalmente.

Por exemplo. Para o seu exemplo original:

a=(one two three)
echo ${a[*]} # one two three
b=a
echo ${!b[*]} # this would not work, because this notation 
              # gives the indices of the variable b which
              # is a string in this case and could be thought
              # as a array that conatins only one element, so
              # we get 0 which means the first element
c='a[*]'
echo ${!c} # this will do exactly what you want in the first
           # place

Para o último cenário real, acredito que o código abaixo faria o trabalho.

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST="LIST_$whichone"[*]
LIST=( "${!_LIST}" ) # Of course for indexed array only 
                     # and not a sparse one

É melhor usar a notação "${var[@]}"que evita bagunçar a $IFSexpansão dos parâmetros e. Aqui está o código final.

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST="LIST_$whichone"[@]
LIST=( "${!_LIST}" ) # Of course for indexed array only 
                     # and not a sparse one
                     # It is essential to have ${!_LIST} quoted
weynhamz
fonte
8

Você precisa copiar os elementos explicitamente. Para uma matriz indexada:

b=("${a[@]}")

Para uma matriz associativa (observe que aé o nome da variável da matriz, não uma variável cujo valor seja o nome de uma variável da matriz):

typeset -A b
for k in "${!a[@]}"; do b[$k]=${a[$k]}; done

Se você tiver o nome da variável em uma matriz, poderá usar o método elemento a elemento com uma etapa extra para recuperar as chaves.

eval "keys=(\"\${!$name[@]}\")"
for k in "${keys[@]}"; do eval "b[\$k]=\${$name[\$k]}"; done

(Aviso, o código nesta postagem foi digitado diretamente em um navegador e não foi testado.)

Gilles 'SO- parar de ser mau'
fonte
Você está certo, mas infelizmente isso não resolve o meu problema (meu exemplo foi muito simplista). Forneci uma atualização para ficar mais claro sobre minha intenção.
Eric Smith
@ Eric Eu acho que em ksh / bash você precisa eval nessa fase. Veja minha edição.
Gilles 'SO- stop be evil'
+1, mas de acordo com seu aviso, isso realmente não funciona (o último bit de código), mas precisa apenas de alguns pequenos ajustes. Especificamente, ele deve estar \${!$name[@]}na primeira linha, para que a primeira expansão seja apenas '$ name' e ${!a[@]}seja salva para o eval, e a mesma coisa no loop for, com \${$name}. As outras duas barras invertidas antes de '$ k' também não são estritamente necessárias.
krb686
2

${!b[*]}expande para os índices usados ​​na matriz b.

O que você gostaria que tem de ser feito em duas etapas, por isso evalvai ajudar: eval echo \${$b[*]}. (Observe o \que garante que a primeira $passará na primeira etapa, a expansão variável e será expandida apenas na segunda etapa eval).

De acordo com o Parâmetro, a expansão !é usada para expansão indireta ( {!a}), nomes correspondentes ao prefixo ( ${!a*}) e lista de chaves de matriz ( ${!a[*]}). Como a Lista de chaves da matriz possui a mesma sintaxe que a expansão indireta pretendida + a expansão do elemento da matriz, a versão mais recente não é suportada.

homem a trabalhar
fonte
2
${!a}se expande para o valor da variável cujo nome é $a. Isso é descrito de maneira bastante detalhada no manual, no parágrafo que começa com "Se o primeiro caractere do parâmetro for um ponto de exclamação ( !), é introduzido um nível de indireção variável".
Gilles 'SO- stop be evil'
Sim - @Gilles está certo, mas @manatwork, em segunda leitura, notei que ${!é meio ambíguo, já que se você está lidando com uma matriz, o comportamento é diferente.
Eric Smith
@Gilles, você está certo nessa frase, mas infelizmente não se aplica como "As exceções a isso são as expansões de $ {! Prefix *} e $ {! Name [@]} descritas abaixo". Mas minha resposta é certamente uma bagunça ambígua, então vou editá-la.
manatwork
0

Para acessar matrizes indiretamente, basta adicionar [@]à variável indireta b=a[@].

Se essas variáveis ​​estiverem definidas:

a=(one two three)
printf '<%s> ' "${a[@]}"; echo

Então, isso funcionará:

b="a[@]"
printf '<%s> ' "${!b}"

Ou simplesmente:

echo "${!b}"

Essa matriz pode ser copiada como esta:

newArr=( "${!b}" )

E depois impresso com:

declare -p newArr

Em um script:

#!/bin/bash
a=(one two three)
echo "original array"
printf '<%s> ' "${a[@]}"; echo

echo "Indirect array with variable b=a[@]"
b="a[@]"
printf '<%s> ' "${!b}"; echo

echo "New array copied in newArr, printed with declare"
newArr=( "${!b}" )
declare -p newArr

Obviamente, todas as opções acima copiarão uma matriz não esparsa. Um em que todos os índices têm um valor.

matrizes esparsas

Uma matriz esparsa é aquela que pode ter elementos não definidos.
Por exemplo, a[8]=1234define um elemento e, no bash, de 0 a 7 não existe.

Para copiar essa matriz esparsa, use este método

  1. Imprima a matriz antiga:

    $ oldarr[8]=1234
    $ declare -p oldarr
    declare -a oldarr=([8]="1234")
  2. Substitua o nome da matriz e capture a sequência:

    $ str=$(declare -p oldarr | sed 's/oldarr=/newarr=/')
  3. Para avaliar a string criada, a nova matriz foi definida:

    $ eval "$str"
    $ declare -p newarr
    declare -a newarr=([8]="1234")
Isaac
fonte