Contar o número de elementos na matriz bash, em que o nome da matriz é dinâmico (isto é, armazenado em uma variável)

10

Breve declaração da questão:

Existe um método bash embutido para contar o número de elementos na matriz bash, onde o nome da matriz é dinâmico (ou seja, armazenado em uma variável), sem recurso para fazer uma cópia completa de a matriz ou usando eval ?

Mais informações:

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

  • Determine o tamanho de uma matriz:
    myArr=(A B C); echo ${#myArr[@]} .
  • Refere-se indiretamente a uma variável pelo nome:
    NAME=myVar; echo ${!NAME}
    (isso também se aplica a elementos de 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 todas FALHAM:

  # 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 das opções acima, mas ainda não encontrei nada que funcionasse 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: Copie a Matriz

Atribua a matriz a outra variável (com nomes estáticos) 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 embutido (isto é, sintaxe de substituição de parâmetro) no bash para determinar o comprimento de um array indiretamente? Se não, qual é a maneira mais eficiente de fazer isso? Eu suponho que é o método eval acima, mas há problemas de segurança ou desempenho com eval ?

    
por ricovox 10.11.2015 / 23:53

2 respostas

4

você deve lidar com essas coisas nos índices de avaliação. e você pode indiretamente através de os índices da sua variável indireta se você fizer 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 os índices de bash são baseados em 0, a contagem total de objetos de matriz sempre funcionará para um mais do que o índice de conjunto 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 alguma.

Se um não for fornecido:

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

... não há mal nenhum.

No loop eu rastreio uma variável $i ndex e verifico se ela é pelo menos tão grande quanto $c ount. Quando é menor, eu expandi o $r eference var para a[i] porque é um índice válido, mas quando é igual ou maior, eu expandi o $r ef para todo o $a rray.

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)'
    
por 11.11.2015 / 14:57
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
    
por 11.11.2015 / 01:52