Zählen Sie die Anzahl der Elemente im Bash-Array, wobei der Name des Arrays dynamisch ist (dh in einer Variablen gespeichert ist).

11

Kurze Erklärung der Frage:

Gibt es eine integrierte Bash-Methode, um die Anzahl der Elemente im Bash-Array zu zählen, wobei der Name des Arrays dynamisch ist (dh in einer Variablen gespeichert ist), ohne eine vollständige Kopie des Arrays zu erstellen oder zu verwenden eval?

Mehr Informationen:

Mit der Bash-Parametersubstitution können Sie Folgendes tun:

  • Bestimmen Sie die Länge eines Arrays :
    myArr=(A B C); echo ${#myArr[@]}.
  • Indirekt auf eine Variable mit Namen verweisen:
    NAME=myVar; echo ${!NAME}
    (Dies gilt auch für Array-Elemente):
    NAME=myArr[1]; echo ${!NAME}

Aber wenn der Name eines Arrays in einer anderen Variablen gespeichert ist, wie kann man die Anzahl der Elemente im Array bestimmen? (Man könnte dies als eine Kombination der beiden oben genannten Parametersubstitutionen betrachten.) Zum Beispiel:

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?

Unten sind mehrere Versuche, die alle fehlschlagen:

  # 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

Ich habe auch einige andere Varianten der oben genannten ausprobiert, aber noch nichts gefunden, was ohne Folgendes funktioniert: (A) Erstellen einer Kopie des Arrays oder (B) Verwenden von eval.

Arbeitsmethoden:

Es gibt einige Möglichkeiten, dies zu lösen, die wahrscheinlich nicht optimal sind (aber korrigieren Sie mich, wenn ich falsch liege):

Methode 1: Kopieren Sie das Array

Weisen Sie das Array einer anderen (statisch benannten) Variablen zu und ermitteln Sie die Anzahl der darin enthaltenen Elemente.

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

Methode 2: Verwenden eval

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

Zusammenfassung:

Gibt es eine integrierte Methode (dh Parametersubstitutionssyntax) in bash, um die Länge eines Arrays indirekt zu bestimmen? Wenn nicht, wie geht das am effizientesten? Ich nehme an, es ist die evaloben beschriebene Methode, aber gibt es Sicherheits- oder Leistungsprobleme mit eval?

drwatsoncode
quelle
2
Pfui. Verschachtelte Variablen. Ich würde jeden Ansatz überdenken, der mich hierher gebracht hat, als verschachtelte Variablen zu verwenden. Was ist das eigentliche Problem hier?
Muru
1
Das ist eine interessante Frage. Das einzige, wovor ich Sie warnen würde, ist anzunehmen, dass etwas ein Leistungsproblem hat oder nicht. Während ziemlich strenger Tests zur Optimierung sehr großer Bash-Skripte stellte ich fest, dass einige Bash-Buildins hinsichtlich der Leistung schrecklich waren, indem ich einfach einen Starttest in einem großen Skript entfernte, der das verwendete, was Sie als effizient erwartet hatten, d. H. Tatsächlich verlangsamte diese einzelne Zeile die gesamte Ausführung um etwa 10 bis 20%. Testmethoden in großen Schleifen mit Timern, Ergebnisse können Sie überraschen.
Lizardx
2
bash namerefs? . declare -n ref=abc; abc=(A B C D); printf '%s\n' "${ref[@]}"
Iruvar
@muru - Dies ist nur eine Semantik, aber der Begriff "verschachtelte Variablen" bezieht sich eher auf Bash vor Version 2. Bash v2 hat eine Syntax für "indirekte Variablenreferenzen" hinzugefügt. Ich frage nur, ob es eine bestimmte Syntax gibt, um die Länge eines indirekt referenzierten Arrays zu ermitteln. Ich gehe davon aus, dass die Bash-Autoren sich nicht die Mühe gemacht hätten, eine variable Indirektion für Skalare und Arrays zu implementieren, wenn dies keine angeforderte, nützliche Technik gewesen wäre - nicht einfach ein Hack, der ein sofortiges "Ugh" rechtfertigt, obwohl ich sicher bin, dass dies umstritten ist .
Drwatsoncode
1
Ich habe ein bisschen einen Benchmark gemacht: time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/nullund ähnlich mit e=$c[@]; d=("${!e}); echo ${#d[@]}in der Schleife. Die Auswertung dauerte etwa 90% der Zeit, die für das Kopieren benötigt wurde. Und ich nehme an, dass die Lücke nur größer wird, je größer das Array und seine Elemente sind.
Muru

Antworten:

4

Sie sollten dieses Zeug in den Indexbewertungen behandeln. und Sie können indirekt über die Indizes Ihrer Indirektionsvariablen gehen, wenn Sie daraus ein Array machen.

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

Da bashdie Indizes auf 0 basieren, wird die Gesamtzahl der Array-Objekte immer um eins höher sein als der höchste festgelegte Index.

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

this index is unset 5

... der Parameter wird auf das Standardwort erweitert, falls vorhanden.

Wenn einer nicht angegeben ist:

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

5

... es wird kein Schaden angerichtet.

In der Schleife verfolge ich eine $index-Variable und überprüfe, ob sie mindestens so groß wie $count ist. Wenn es kleiner ist, erweitere ich die $reference var auf, a[i]weil es ein gültiger Index ist, aber wenn es gleich oder größer ist, erweitere ich die $ref auf das gesamte $aRray.

Hier ist es in einer Funktion:

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
quelle
Lassen Sie uns diese Diskussion im Chat fortsetzen .
Drwatsoncode
0

Bash 4.3 Namerefs sind ein Glücksfall. Sie können dies jedoch tun:

$ myArr=(A B C D)
$ NAME=myArr
$ tmp="${NAME}[@]"
$ copy=( "${!tmp}" )
$ echo "${#copy[@]}"
4
Glenn Jackman
quelle
Vielen Dank für Ihre Antwort, aber Ihre Antwort ist die, die ich bereits im Abschnitt "Methode 1: Kopieren des Arrays" beschrieben habe. In der Frage wurde auch ausdrücklich angegeben, dass die Array-Länge bestimmt werden sollte, "ohne eine vollständige Kopie des Arrays zu erstellen", was genau das ist, was Sie getan haben.
Drwatsoncode