Conta il numero di elementi nella matrice bash, in cui il nome della matrice è dinamico (ovvero memorizzato in una variabile)


11

Breve affermazione della domanda:

Esiste un metodo bash incorporato per contare il numero di elementi nell'array bash, in cui il nome dell'array è dinamico (ovvero memorizzato in una variabile), senza ricorrere alla creazione di una copia completa dell'array o all'utilizzo eval?

Maggiori informazioni:

Usando la sostituzione dei parametri bash, si può fare quanto segue:

  • Determinare la lunghezza di un array:
    myArr=(A B C); echo ${#myArr[@]}.
  • Indirettamente fare riferimento a una variabile per nome:
    NAME=myVar; echo ${!NAME}
    (questo vale anche per gli elementi dell'array):
    NAME=myArr[1]; echo ${!NAME}

Ma se il nome di un array è memorizzato in un'altra variabile, come si può determinare il numero di elementi nell'array? (Si potrebbe considerare questa una combinazione delle due sostituzioni di parametri precedenti). Ad esempio:

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?

Di seguito sono riportati più tentativi che tutti 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

Ho anche provato alcune altre varianti di quanto sopra, ma non ho ancora trovato nulla che funzioni senza: (A) facendo una copia dell'array o (B) usando eval.

Metodi di lavoro:

Ci sono un paio di modi per risolvere questo problema che probabilmente non sono ottimali (ma correggimi se sbaglio):

Metodo 1: copia l'array

Assegna l'array a un'altra variabile (denominata staticamente) e ottieni il numero di elementi in essa contenuti.

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

Metodo 2: utilizzare eval

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

Sommario:

Esiste un metodo incorporato (ad es. Sintassi di sostituzione dei parametri) in bash per determinare indirettamente la lunghezza di un array? In caso contrario, qual è il modo più efficiente per farlo? Presumo che sia il evalmetodo sopra, ma ci sono problemi di sicurezza o prestazioni con eval?


2
Ugh. Variabili nidificate. Ripenserei a qualsiasi approccio mi portasse qui piuttosto che usare variabili nidificate. Qual è il vero problema qui?
muru,

1
È una domanda interessante L'unica cosa che ti metterei in guardia contro è presumere che qualcosa abbia o non abbia un problema di prestazioni. Durante i test abbastanza rigorosi per ottimizzare script bash molto grandi, alcuni builtin bash erano terribili in termini di prestazioni, infatti, semplicemente rimuovendo un test di avvio in uno script di grandi dimensioni, che utilizzava ciò che ci si poteva aspettare che fosse efficiente, vale a dire , l'espansione variabile, infatti, quella singola linea ha rallentato l'intera esecuzione di circa il 10-20%. Metodi di prova in grandi loop con timer, i risultati potrebbero sorprenderti.
Lizardx,

2
bash namerefs? . declare -n ref=abc; abc=(A B C D); printf '%s\n' "${ref[@]}"
Iruvar,

@muru - Questa è solo una semantica, ma il termine "variabili nidificate" si riferisce maggiormente a bash prima della versione 2. Bash v2 ha aggiunto una sintassi per "riferimenti a variabili indirette". Sto solo chiedendo se esiste una sintassi specifica per ottenere la lunghezza di un array a riferimento indiretto. Suppongo che gli autori bash non si sarebbero adoperati per implementare l'indirizzamento variabile per scalari e array se non fosse una tecnica utile e richiesta - non semplicemente un hack che garantisse un "Ugh" immediato, anche se sono sicuro che sia discutibile .
drwatsoncode

1
Ho fatto un po 'di benchmark: time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/nulle similmente con e=$c[@]; d=("${!e}); echo ${#d[@]}nel loop. La valutazione ha richiesto circa il 90% del tempo impiegato dalla copia. E suppongo che il divario aumenterà solo con l'aumentare dell'array e dei suoi elementi.
muru,

Risposte:


4

dovresti gestire quella roba negli indici degli indici. e puoi indirizzare attraverso gli indici della tua variabile di indiretta se lo rendi un array.

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

Poiché bashgli indici sono basati su 0, il conteggio totale degli oggetti dell'array funzionerà sempre su uno in più rispetto all'indice impostato più alto, e quindi:

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

this index is unset 5

... il parametro si espande alla parola predefinita se ne viene fornita una.

Se uno non viene fornito:

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

5

... non è stato fatto nulla di male.

Nel loop seguo una $ivariabile ndex e controllo se è grande almeno quanto $count. Quando è minore espanderò l' $rEference var a[i]perché è un indice valido, ma quando è uguale o maggiore espanderò l' $ref all'intero $array.

Eccolo in una funzione:

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)'


0

bash 4.3 namerefs sono una manna dal cielo. Tuttavia, puoi farlo:

$ myArr=(A B C D)
$ NAME=myArr
$ tmp="${NAME}[@]"
$ copy=( "${!tmp}" )
$ echo "${#copy[@]}"
4

Grazie per la risposta, ma la tua risposta è quella che ho già descritto nella sezione "Metodo 1: Copia l'array". La domanda affermava inoltre specificamente che la lunghezza dell'array doveva essere determinata "senza ricorrere a fare una copia completa dell'array", che è esattamente ciò che hai fatto.
drwatsoncode
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.