Contar o número de elementos no array bash, onde o nome do array é dinâmico (ou seja, armazenado em uma variável)

11

Breve declaração da pergunta:

Existe um método bash interno para contar o número de elementos no array bash, onde o nome do array é dinâmico (ou seja, armazenado em uma variável), sem recorrer a fazer uma cópia completa do array ou usá-lo eval?

Mais Informações:

Usando a substituição do parâmetro bash, pode-se fazer o seguinte:

  • Determinar o comprimento de uma matriz:
    myArr=(A B C); echo ${#myArr[@]}.
  • Referencie indiretamente uma variável pelo nome:
    NAME=myVar; echo ${!NAME}
    (isso também se aplica aos elementos da matriz):
    NAME=myArr[1]; echo ${!NAME}

Mas se o nome de uma matriz é armazenado em outra variável, como se pode determinar o número de elementos na matriz? (Pode-se considerar isso uma combinação das duas substituições de parâmetro acima.) Por exemplo:

myArr=(A B C D)
NAME=myArr
# Get the number of elements in the array indirectly referenced by NAME.
count=${#$NAME[@]}  # This syntax is invalid. What is the right way?

Abaixo estão várias tentativas que todos FAIL:

  # Setup for following attempts:
  myArr=(A B C D)
  NAME=myArr
  EXPR1=$NAME[@]          # i.e. EXPR1='myArr[@]'
  EXPR2=#$NAME[@]         # i.e. EXPR2='#myArr[@]'

  # Failed attempts to get the lengh of the array indirectly:
  1.  count=${#$NAME[@]}  # ERROR: bash: ...: bad substitution
  2.  count=${#!EXPR1}    # ERROR: bash: !EXPR}: event not found
  3.  count=${#\!EXPR1}   # ERROR: bash: ...: bad substitution
  4.  count=${!#EXPR1}    # ERROR: bash: ...: bad substitution
  5.  count=${!EXPR2}     # Returns NULL

Eu também tentei algumas outras variantes do acima, mas ainda não encontrei nada que funcione sem: (A) fazer uma cópia da matriz ou (B) usando eval.

Métodos de trabalho:

Existem algumas maneiras de resolver isso que provavelmente não são ideais (mas corrija-me se estiver errado):

Método 1: Copiar a matriz

Atribua a matriz a outra variável (nomeada estaticamente) e obtenha o número de elementos nela.

EXPR=$NAME[@]
arrCopy=( "${!EXPR}" )
count=${#arrCopy}

Método 2: Usar eval

EXPR="count=\${#$NAME[@]}"  # i.e. 'count=${myArr[@]}'
eval $EXPR
# Now count is set to the length of the array

Resumo:

Existe algum método interno (isto é, sintaxe de substituição de parâmetro) no bash para determinar indiretamente o comprimento de uma matriz? Caso contrário, qual é a maneira mais eficiente de fazer isso? Presumo que seja o evalmétodo acima, mas há problemas de segurança ou desempenho com eval?

drwatsoncode
fonte
2
Ugh. Variáveis ​​aninhadas. Eu repensaria qualquer abordagem que me trouxesse aqui do que usar variáveis ​​aninhadas. Qual é o problema real aqui?
Muru
1
É uma pergunta interessante. A única coisa que eu alertaria contra você seria assumir que algo tem ou não um problema de desempenho. Eu descobri, durante testes bastante rigorosos, para otimizar scripts bash muito grandes que alguns componentes bash eram terríveis em termos de desempenho, na verdade, simplesmente removendo um teste de inicialização em um script grande, que usava o que você esperava que fosse eficiente, ou seja, , expansão variável, de fato, essa linha única diminuiu a execução inteira em cerca de 10 a 20%. Métodos de teste em grandes loops com temporizadores, os resultados podem surpreendê-lo.
Lizardx
2
bash namerefs? . declare -n ref=abc; abc=(A B C D); printf '%s\n' "${ref[@]}"
Iruvar
@ muru - Isso é apenas semântica, mas o termo "variáveis ​​aninhadas" se refere mais ao bash antes da versão 2. O Bash v2 adicionou uma sintaxe para "referências às variáveis ​​indiretas". Estou apenas perguntando se existe uma sintaxe específica para obter o comprimento de uma matriz indiretamente referenciada. Suponho que os autores do bash não teriam se esforçado para implementar variáveis ​​indiretas para escalares e matrizes se não fosse uma técnica útil e solicitada - não simplesmente um hackeamento que justifique um "Ugh" imediato, embora eu tenha certeza que isso é discutível .
Drwatsoncode 11/11/2015
1
Eu fiz um pouco de referência time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/null:, e da mesma forma com e=$c[@]; d=("${!e}); echo ${#d[@]}no loop. A avaliação levou cerca de 90% do tempo gasto na cópia. E suponho que essa diferença só aumentará quanto maior a matriz e seus elementos.
muru 11/11/2015

Respostas:

4

você deve lidar com essas coisas nos índices. e você pode indiretamente através dos índices da sua variável de indireção, se criar uma matriz.

a=(abc1 def2 ghi3 jkl4 mno5)
r=('a[c=${#a[@]}]' a\[i] a\[@])
for   i in   0 1 2 3 4 5
do    c=
      printf "<%s>\n" "${!r-${!r[i<c?1:2]}}"
      printf "\n\tindex is $i and count is $c\n\n"
done

<abc1>

    index is 0 and count is 5

<def2>

    index is 1 and count is 5

<ghi3>

    index is 2 and count is 5

<jkl4>

    index is 3 and count is 5

<mno5>

    index is 4 and count is 5

<abc1>
<def2>
<ghi3>
<jkl4>
<mno5>

    index is 5 and count is 5

Como bashos índices são baseados em 0, a contagem total de objetos de matriz sempre funcionará com um a mais que o índice definido mais alto e, portanto:

c=
echo "${a[c=${#a[@]}]-this index is unset}" "$c"

this index is unset 5

... o parâmetro se expande para a palavra padrão, se houver.

Se um não for fornecido:

c=
${!r}
echo "$c"

5

... não há mal feito.

No loop, acompanho uma $ivariável ndex e verifico se ela é pelo menos tão grande quanto $count. Quando é menor, $rexpiro a eference var para a[i]porque é um índice válido, mas quando é igual ou superior, expiro o $ref para todo o $araio.

Aqui está em uma função:

ref_arr(){
    local    index=-1 count=
    local    ref=(   "$1[ count= \${#$1[@]}  ]"
                     "$1[ index ]"    "$1[ @ ]"
    )  &&    printf  "input array '%s' has '%d' members.\n" \
                     "$1"  "${!ref-${count:?invalid array name: "'$1'"}}"
    while    [ "$((index+=1))" -lt "$count"  ]
    do       printf  "$1[$index]  ==  '%s'\n"  "${!ref[1]}"
    done
}
some_array=(some "dumb
            stuff" 12345\'67890 "" \
          '$(kill my computer)')
ref_arr some_array
ref_arr '$(echo won'\''t work)'

input array 'some_array' has '5' members.
some_array[0]  ==  'some'
some_array[1]  ==  'dumb
                stuff'
some_array[2]  ==  '12345'67890'
some_array[3]  ==  ''
some_array[4]  ==  '$(kill my computer)'
bash: count: invalid array name: '$(echo won't work)'
mikeserv
fonte
Vamos continuar esta discussão no chat .
Drwatsoncode
0

bash 4.3 namerefs são uma dádiva de Deus. No entanto, você pode fazer isso:

$ myArr=(A B C D)
$ NAME=myArr
$ tmp="${NAME}[@]"
$ copy=( "${!tmp}" )
$ echo "${#copy[@]}"
4
Glenn Jackman
fonte
Obrigado por responder, mas sua resposta é a que eu já descrevi na seção "Método 1: Copiar a matriz". A pergunta também afirmou especificamente que o comprimento da matriz deve ser determinado "sem recorrer a uma cópia completa da matriz", que é exatamente o que você fez.
Drwatsoncode 11/11/2015