Cuente el número de elementos en la matriz bash, donde el nombre de la matriz es dinámico (es decir, almacenado en una variable)

11

Breve declaración de la pregunta:

¿Existe un método bash incorporado para contar el número de elementos en la matriz bash, donde el nombre de la matriz es dinámico (es decir, almacenado en una variable), sin recurrir a hacer una copia completa de la matriz o usarlo eval?

Más información:

Usando la sustitución de parámetros bash, uno puede hacer lo siguiente:

  • Determinar la longitud de una matriz:
    myArr=(A B C); echo ${#myArr[@]}.
  • Indirectamente hacer referencia a una variable por su nombre:
    NAME=myVar; echo ${!NAME}
    (esto también se aplica a los elementos de la matriz):
    NAME=myArr[1]; echo ${!NAME}

Pero si el nombre de una matriz se almacena en otra variable, ¿cómo se puede determinar el número de elementos en la matriz? (Uno podría considerar esto como una combinación de las dos sustituciones de parámetros anteriores). Por ejemplo:

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?

A continuación hay varios intentos que FALLAN:

  # 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

También probé algunas otras variantes de lo anterior, pero todavía no he encontrado nada que funcione sin: (A) hacer una copia de la matriz o (B) usando eval.

Métodos de trabajo:

Hay un par de formas de resolver esto que probablemente no sean óptimas (pero corríjame si me equivoco):

Método 1: copie la matriz

Asigne la matriz a otra variable (con nombre estático) y obtenga el número de elementos en ella.

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

Método 2: uso eval

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

Resumen:

¿Hay algún método incorporado (es decir, sintaxis de sustitución de parámetros) en bash para determinar la longitud de una matriz indirectamente? Si no, ¿cuál es la forma más eficiente de hacer esto? Supongo que es el evalmétodo anterior, pero ¿existen problemas de seguridad o rendimiento eval?

drwatsoncode
fuente
2
Ugh Variables anidadas Replantearía cualquier enfoque que me trajera aquí que usar variables anidadas. ¿Cuál es el problema real aquí?
muru
1
Es una pregunta interesante. La única cosa contra la que le advertiría es asumir que algo tiene o no un problema de rendimiento. Durante las pruebas bastante rigurosas, para optimizar los scripts de bash muy grandes, descubrí que algunos bash builtins eran terribles en términos de rendimiento, de hecho, simplemente eliminando una prueba de inicio en un script grande, que utilizaba lo que podría haber esperado que fuera eficiente, es decir , expansión variable, de hecho, esa sola línea ralentizó la ejecución completa en aproximadamente un 10 a 20%. Métodos de prueba en bucles grandes con temporizadores, los resultados pueden sorprenderlo.
Lizardx
2
bash namerefs? . declare -n ref=abc; abc=(A B C D); printf '%s\n' "${ref[@]}"
iruvar
@muru: esto es solo semántica, pero el término "variables anidadas" se relaciona más con bash antes de la versión 2. Bash v2 agregó una sintaxis para "referencias de variables indirectas". Solo pregunto si hay una sintaxis específica para obtener la longitud de una matriz indirectamente referenciada. Supongo que los autores de bash no se habrían esforzado por implementar indirección variable para escalares y matrices si no fuera una técnica útil y solicitada, no simplemente un truco que garantiza un "Ugh" inmediato, aunque estoy seguro de que es discutible .
drwatsoncode
1
Hice un poco de referencia: time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/nully de manera similar con e=$c[@]; d=("${!e}); echo ${#d[@]}en el bucle. La evaluación tomó aproximadamente el 90% del tiempo que se tardó en copiar. Y supongo que esa brecha solo aumentará cuanto más grande sea la matriz y sus elementos.
muru

Respuestas:

4

deberías manejar esas cosas en el índice evals. y puede indirecta a través de los índices de su variable de indirección si la convierte en una 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

Debido a que bashlos índices están basados ​​en 0, el recuento total de objetos de matriz siempre funcionará en uno más que el índice establecido más alto, y así:

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

this index is unset 5

... el parámetro se expande a la palabra predeterminada si se proporciona alguno.

Si no se proporciona uno:

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

5

... no hay daño hecho.

En el bucle, sigo una $ivariable ndex y verifico si es al menos tan grande como $count. Cuando es menor, $ramplío la eferencia var a[i]porque es un índice válido, pero cuando es igual o mayor, expando el $ref a toda la $amatriz.

Aquí está en una función:

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
fuente
Continuemos esta discusión en el chat .
drwatsoncode
0

bash 4.3 namerefs son un regalo del cielo. Sin embargo, puedes hacer esto:

$ myArr=(A B C D)
$ NAME=myArr
$ tmp="${NAME}[@]"
$ copy=( "${!tmp}" )
$ echo "${#copy[@]}"
4
Glenn Jackman
fuente
Gracias por responder, pero su respuesta es lo que ya describí en la sección "Método 1: Copie la matriz". La pregunta también indicó específicamente que la longitud de la matriz debe determinarse "sin recurrir a hacer una copia completa de la matriz", que es exactamente lo que hizo.
drwatsoncode