Compter le nombre d'éléments dans le tableau bash, où le nom du tableau est dynamique (c'est-à-dire stocké dans une variable)

11

Bref énoncé de la question:

Existe-t-il une méthode bash intégrée pour compter le nombre d'éléments dans le tableau bash, où le nom du tableau est dynamique (c'est-à-dire stocké dans une variable), sans recourir à une copie complète du tableau ou à l'utilisation eval?

Plus d'information:

En utilisant la substitution de paramètres bash, on peut faire ce qui suit:

  • Déterminer la longueur d'un tableau:
    myArr=(A B C); echo ${#myArr[@]}.
  • Référencez indirectement une variable par son nom:
    NAME=myVar; echo ${!NAME}
    (cela s'applique également aux éléments du tableau):
    NAME=myArr[1]; echo ${!NAME}

Mais si le nom d'un tableau est stocké dans une autre variable, comment déterminer le nombre d'éléments dans le tableau? (On pourrait considérer cela comme une combinaison des deux substitutions de paramètres ci-dessus.) Par exemple:

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?

Vous trouverez ci-dessous plusieurs tentatives qui ont toutes échoué:

  # 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

J'ai également essayé d'autres variantes de ce qui précède, mais je n'ai encore rien trouvé qui fonctionne sans: (A) faire une copie du tableau ou (B) en utilisant eval.

Les méthodes de travail:

Il existe deux façons de résoudre ce problème qui ne sont probablement pas optimales (mais corrigez-moi si je me trompe):

Méthode 1: copiez le tableau

Attribuez le tableau à une autre variable (nommée statiquement) et obtenez le nombre d'éléments qu'elle contient.

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

Méthode 2: utiliser eval

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

Résumé:

Existe-t-il une méthode intégrée (c'est-à-dire une syntaxe de substitution de paramètres) dans bash pour déterminer indirectement la longueur d'un tableau? Sinon, quelle est la manière la plus efficace de procéder? Je suppose que c'est la evalméthode ci-dessus, mais y a-t-il des problèmes de sécurité ou de performances avec eval?

drwatsoncode
la source
2
Pouah. Variables imbriquées. Je repenserais l'approche qui m'a amené ici plutôt que d'utiliser des variables imbriquées. Quel est le vrai problème ici?
muru
1
C'est une question intéressante. La seule chose contre laquelle je vous mets en garde est de supposer que quelque chose a ou n'a pas un problème de performances. J'ai trouvé lors de tests assez rigoureux pour optimiser de très gros scripts bash que certains builds bash étaient terribles en termes de performances, en fait, en supprimant simplement un test de démarrage dans un grand script, qui utilisait ce que vous attendiez pour être efficace, à savoir , l'expansion variable, en fait, cette ligne unique a ralenti l'ensemble de l'exécution d'environ 10 à 20%. Testez les méthodes en grandes boucles avec des minuteries, les résultats peuvent vous surprendre.
Lizardx
2
bash namerefs? . declare -n ref=abc; abc=(A B C D); printf '%s\n' "${ref[@]}"
iruvar
@muru - Ce n'est que de la sémantique, mais le terme "variables imbriquées" se rapporte plus à bash avant la version 2. Bash v2 a ajouté une syntaxe pour les "références de variables indirectes". Je demande simplement s'il existe une syntaxe spécifique pour obtenir la longueur d'un tableau référencé indirectement. Je suppose que les auteurs bash n'auraient pas fait l'effort d'implémenter l'indirection variable pour les scalaires et les tableaux si ce n'était pas une technique utile et demandée - pas simplement un hack garantissant un "Ugh" immédiat, bien que je sois sûr que cela soit discutable .
drwatsoncode
1
J'ai fait un peu de référence time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/null:, et de même avec e=$c[@]; d=("${!e}); echo ${#d[@]}dans la boucle. L'évaluation a pris environ 90% du temps nécessaire à la copie. Et je suppose que l'écart ne fera qu'augmenter plus le tableau et ses éléments seront grands.
muru

Réponses:

4

vous devez gérer ce genre de choses dans les index. et vous pouvez indirectement via les indices de votre variable d'indirection si vous en faites un tableau.

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

Étant donné que bashles indices de sont basés sur 0, le nombre total d'objets de tableau correspondra toujours à un de plus que l'index défini le plus élevé, et donc:

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

this index is unset 5

... le paramètre se développe jusqu'au mot par défaut s'il en est.

Si aucun n'est fourni:

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

5

... il n'y a pas de mal.

Dans la boucle, je $idépiste une variable ndex et vérifie si elle est au moins aussi grande que $count. Quand il est moindre, $rj'étends la var eference a[i]car c'est un index valide, mais quand il est égal ou supérieur, j'étends $ref à l'ensemble du $array.

Le voici dans une fonction:

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
la source
Continuons cette discussion dans le chat .
drwatsoncode
0

bash 4.3 namerefs est une aubaine. Cependant, vous pouvez le faire:

$ myArr=(A B C D)
$ NAME=myArr
$ tmp="${NAME}[@]"
$ copy=( "${!tmp}" )
$ echo "${#copy[@]}"
4
glenn jackman
la source
Merci d'avoir répondu, mais votre réponse est celle que j'ai déjà décrite dans la section "Méthode 1: copier le tableau". La question précisait également que la longueur du tableau devait être déterminée "sans avoir recours à une copie complète du tableau", ce qui est exactement ce que vous avez fait.
drwatsoncode