Порахуйте кількість елементів у масиві bash, де ім'я масиву є динамічним (тобто зберігається у змінній)


11

Короткий виклад питання:

Чи існує вбудований метод bash для підрахунку кількості елементів у масиві bash, де ім'я масиву є динамічним (тобто зберігається у змінній), не вдаючись до повноцінної копії масиву чи використання eval?

Більше інформації:

За допомогою підстановки параметрів bash можна зробити наступне:

  • Визначити довжину масиву:
    myArr=(A B C); echo ${#myArr[@]}.
  • Опосередковане посилання на змінну за назвою:
    NAME=myVar; echo ${!NAME}
    (це також стосується елементів масиву):
    NAME=myArr[1]; echo ${!NAME}

Але якщо ім'я масиву зберігається в іншій змінній, як можна визначити кількість елементів масиву? (Можна вважати це комбінацією двох вищезазначених підстановок параметрів.) Наприклад:

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?

Нижче наведено кілька спроб, які всі НЕПРАВНО

  # 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

Я також спробував деякі інші варіанти вищезазначеного, але досі не знайшов нічого, що працює без того, щоб: (A) створити копію масиву або (B) за допомогою eval.

Методи роботи:

Є кілька способів вирішення цього питання, які, ймовірно, не є оптимальними (але виправте мене, якщо я помиляюся):

Спосіб 1: Скопіюйте масив

Призначте масив до іншої змінної (статично названої) та отримайте кількість елементів у ній.

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

Спосіб 2: Використання eval

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

Підсумок:

Чи є якийсь вбудований метод (тобто синтаксис заміщення параметрів) у bash, щоб визначати довжину масиву опосередковано? Якщо ні, то який найефективніший спосіб це зробити? Я припускаю, що це evalметод вище, але чи є проблеми із безпекою чи продуктивністю eval?


2
Тьфу. Вкладені змінні. Я б переосмислив будь-який підхід мене тут, ніж використовувати вкладені змінні. У чому тут актуальна проблема?
муру

1
Це цікаве питання. Єдине, про що я б застерігав вас - це припустити, що у проблеми є продуктивність чи немає. Я виявив, що під час досить жорсткого тестування для оптимізації дуже великих скриптів bash, що деякі bash вбудовані були жахливими з точки зору продуктивності, насправді, просто видаливши один тест запуску у великому сценарії, який використовував те, що, можливо, ви очікували на ефективність, тобто , змінне розширення, насправді, що один рядок уповільнив виконання всього приблизно на 10-20%. Методи тестування у великих циклах із таймерами, результати можуть вас здивувати.
Лізардкс

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

@muru - Це просто семантика, але термін "вкладені змінні" більше стосується bash до версії 2. Bash v2 додав синтаксис для "непрямих посилань на змінну". Я просто запитую, чи є певний синтаксис, щоб отримати довжину масиву, що посилається побічно. Я припускаю, що автори bash не пішли б на реалізацію змінної непрямості для скалярів і масивів, якби це не запитувана корисна методика - не просто хак, що вимагає негайного "тьфу", хоча я впевнений, що це дискусійно .
drwatsoncode

1
Я зробив трохи орієнтиру:, time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/nullі так само e=$c[@]; d=("${!e}); echo ${#d[@]}в циклі. Евал зайняв близько 90% часу, який займає копіювання. І я припускаю, що розрив лише збільшить більший масив та його елементи.
муру

Відповіді:


4

вам слід обробляти ці речі в індексі овалів. і ви можете опосередковуватись через індекси змінної вашої непрямості, якщо зробите це масив.

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

Оскільки bashіндекси 'є на основі 0, загальна кількість об'єктів масиву завжди працюватиме на один більше, ніж найвищий встановлений індекс, і так:

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

this index is unset 5

... параметр розширюється до слова за замовчуванням, якщо таке надано.

Якщо такий не надається:

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

5

... шкоди немає.

У циклі я відслідковую $iзмінну ndex і перевіряю, чи є вона принаймні такою великою, як і $count. Коли вона менша, я розширюю $reference var, a[i]тому що це дійсний індекс, але коли він дорівнює або більше, я розширюю $ref на весь $aрядок.

Ось це у функції:

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 - знахідка. Однак ви можете зробити це:

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

Дякую за відповідь, але ваша відповідь - це те, що я вже описав у розділі "Спосіб 1: Скопіюйте масив". Питання також конкретно зазначало, що довжину масиву слід визначати "не вдаючись до повноцінної копії масиву", саме це ви зробили.
drwatsoncode
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.