Dizinin adının dinamik olduğu (yani bir değişkende depolanan) bash dizisindeki öğe sayısını sayma


11

Sorunun kısa açıklaması:

Orada yerleşik mı bash yönteminin dizinin adı (yani bir değişkende saklanır) dinamiktir bash dizi öğe sayısını saymak için, olmadan tam dizinin kopya yapmadan veya kullanarak başvurmadan eval?

Daha fazla bilgi:

Bash parametresi ikamesini kullanarak aşağıdakileri yapabilirsiniz:

  • Bir dizinin uzunluğunun belirlenmesi:
    myArr=(A B C); echo ${#myArr[@]}.
  • Bir değişkeni ada göre dolaylı olarak referanslayın:
    NAME=myVar; echo ${!NAME}
    (bu, dizi öğeleri için de geçerlidir):
    NAME=myArr[1]; echo ${!NAME}

Ancak bir dizinin adı başka bir değişkende saklanıyorsa, dizideki öğelerin sayısı nasıl belirlenebilir? (Bu , yukarıdaki iki parametre ikamesinin bir kombinasyonu olarak düşünülebilir .) Örneğin:

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şağıda BAŞARISIZ tüm birden fazla deneme vardır:

  # 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

Ayrıca yukarıdaki diğer bazı varyantları denedim, ama henüz ya da olmadan çalışan bir şey bulamadım: (A) dizinin bir kopyasını yapmak veya (B) kullanarak eval.

Çalışma yöntemleri:

Bunu çözmenin muhtemelen optimal olmayan birkaç yolu vardır (ama yanılıyorsam beni düzeltin):

Yöntem 1: diziyi kopyalama

Diziyi başka bir (statik olarak adlandırılmış) değişkene atayın ve içindeki öğelerin sayısını alın.

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

Yöntem 2: Kullanım eval

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

Özet:

Bir dizinin uzunluğunu dolaylı olarak belirlemek için bash'de yerleşik bir yöntem (yani parametre ikame sözdizimi) var mı? Değilse, bunu yapmanın en etkili yolu nedir? evalYukarıdaki yöntem olduğunu varsayıyorum , ancak güvenlik veya performans sorunları var evalmı?


2
Ugh. İç içe değişkenler. Beni buraya getiren her yaklaşımı iç içe değişkenler kullanmaktan çok düşünürdüm. Burada asıl sorun nedir?
muru

1
Bu ilginç bir soru. Size karşı uyaracağım tek şey, bir şeyin performans sorunu olduğunu varsayalım. Oldukça titiz testler sırasında, bazı bash yapılarının performans açısından korkunç olduğunu, aslında, büyük bir komut dosyasında tek bir başlatma testini kaldırarak, verimli olmasını beklediğiniz şeyi kullanan, yani değişken genişleme, aslında, bu tek satır tüm yürütmeyi yaklaşık% 10 ila 20 oranında yavaşlattı. Zamanlayıcılı büyük döngülerdeki test yöntemleri, sonuçlar sizi şaşırtabilir.
Lizardx

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

@muru - Bu sadece anlambilimdir, ancak "iç içe değişkenler" terimi, sürüm 2'den önce bash ile ilgilidir. Bash v2, "dolaylı değişken başvuruları" için bir sözdizimi ekledi. Ben sadece dolaylı başvurulan dizinin uzunluğunu almak için belirli bir sözdizimi olup olmadığını soruyorum. Bash yazarlarının, istenen, kullanışlı bir teknik olmasaydı, skaler ve diziler için değişken dolaylı uygulama çabalarına gitmeyeceklerini varsayıyorum - tartışmalı olduğundan emin olduğumdan emin olmama rağmen .
drwatsoncode

1
Biraz bir kıyaslama yaptım: time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/nullve benzer şekilde e=$c[@]; d=("${!e}); echo ${#d[@]}döngüde. Değerlendirme kopyalanarak geçirilen sürenin yaklaşık% 90'ını aldı. Ve sanırım boşluk sadece dizi ve elemanları büyür.
muru

Yanıtlar:


4

bu şeyleri indeks düzeylerinde ele almalısınız. ve eğer bir dizi yaparsanız dolaylı değişkeninizin indeksleri üzerinden dolaylı olarak yapabilirsiniz.

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

Çünkü bashbireyin endeksleri 0-dayanır, dizi nesnelerin toplam sayım her zaman daha yüksek ayar indeksi daha fazla birine çalışmak ve böylece edecek:

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

this index is unset 5

... varsa parametre varsayılan sözcüğe genişler.

Biri sağlanmazsa:

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

5

... zararı yok.

$iDöngüde bir ndex değişkenini izlerim ve en azından $count kadar büyük olup olmadığını kontrol ederim . Daha az olduğunda ben geçerli bir dizin olduğu için $reference var genişletmek a[i], ama eşit veya daha büyük olduğunda $ref tüm $aray için genişletmek .

İşte bir işlevi var:

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 bir nimettir. Ancak, bunu yapabilirsiniz:

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

Yanıtınız için teşekkürler, ancak cevabınız "Yöntem 1: Diziyi Kopyala" bölümünde daha önce açıkladığım şey. Soru ayrıca özellikle dizi uzunluğunun "dizinin tam bir kopyasına başvurmadan" belirlenmesi gerektiğini belirtti ki bu da tam olarak yaptığınız şeydi.
drwatsoncode
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.