Тест на підтримку масиву по оболонці


12

Чи існує стислий спосіб тестування підтримки масиву локальною оболонкою Bourne в командному рядку?

Це завжди можливо:

$ arr=(0 1 2 3);if [ "${arr[2]}" != 2 ];then echo "No array support";fi

або тестування для $SHELLверсії та оболонки:

$ eval $(echo "$SHELL --version") | grep version

а потім читати чоловічу сторінку, припускаючи, що я маю доступ до неї. (Навіть там, писати з /bin/bash, я припускаю , що все Bourne-подібні оболонки допускають довгий варіант --version, коли що перерви для KSH , наприклад .)

Я шукаю простий тест, який можна було б без нагляду та включити до розділу Використання на початку сценарію або навіть перед тим, як викликати його.


Я припускаю, що ви хочете обмежитися снарядами, схожими на Борна?
Стефан Шазелас

@ StéphaneChazelas: Так, якщо ви маєте на увазі (не вичерпно) основну групу, що складається з: sh, csh, ksh, tcsh, bash, zsh та близьких друзів. Я не знаю, де яш розміщується в цьому сузір'ї.
Cbhihe

2
cshне є оболонкою бурна. tcshце не один (це cshвиправлено деякі помилки)
cas

1
Зауважте, що $SHELLце краща оболонка користувача, як $EDITORі його бажаний текстовий редактор. Це мало стосується поточної оболонки, що працює.
Стефан Шазелас

1
evalвикористання виводу $SHELL --versionкоду оболонки не має сенсу.
Стефан Шазелас

Відповіді:


12

Припускаючи , що ви хочете обмежитися Bourne-подібних оболонок (багато інших раковин люблять csh, tcsh, rc, esабо fishпідтримку масиви , але написання сценарію сумісні в той же час Bourne-подібні оболонок і тим складно і взагалі безглуздо , оскільки вони інтерпретатори абсолютно різні і несумісні мови), зауважте, що між реалізаціями існують значні відмінності.

Оболонки Борна, що підтримують масиви:

  • ksh88(це перший масив, що реалізує, ksh88 все ще зустрічається, як і kshв більшості традиційних комерційних об'єднань, де це також є основою для sh)

    • масиви є одновимірними
    • Масиви визначаються як set -A array foo barабо set -A array -- "$var" ...якщо ви не можете гарантувати, що $varвони не почнуться з -або +.
    • Індекси масиву починаються з 0.
    • Окремі елементи масиву призначаються як a[1]=value.
    • масиви рідкі. Тобто a[5]=fooбуде працювати, навіть якщо a[0,1,2,3,4]вони не встановлені, і вони залишать їх невстановленими.
    • ${a[5]}для доступу до елемента індексу 5 (необов'язково 6-го елемента, якщо масив розріджений). 5Може бути будь-який арифметичний вираз.
    • розмір масиву та підписка обмежений (до 4096).
    • ${#a[@]} - кількість призначеного елемента в масиві (не найбільший призначений індекс).
    • немає ніякого способу знати список призначених підписок (крім тестування 4096 елементів окремо за допомогою [[ -n "${a[i]+set}" ]]).
    • $aте саме, що ${a[0]}. Тобто масиви якось розширюють скалярні змінні, надаючи їм додаткові значення.
  • pdkshта похідні (це основа для, kshа іноді й shдекількох BSD, і була єдиною відкритою програмою ksh до звільнення джерела ksh93):

    В основному подобається, ksh88але зауважте:

    • Деякі старі реалізації не підтримували set -A array -- foo bar(там вони --не потрібні).
    • ${#a[@]}- це плюс плюс індексу найбільшого призначеного індексу. ( a[1000]=1; echo "${#a[@]}"виводить 1001, навіть якщо масив містить лише один елемент.
    • у нових версіях розмір масиву більше не обмежений (крім розміру цілих чисел).
    • останні версії mkshє кілька додаткових операторів , натхнених з bash, ksh93або zshяк завдання а - ля a=(x y), a+=(z), ${!a[@]}щоб отримати список призначених індексів.
  • zsh. zshМасиви, як правило, краще розроблені та приймають найкращі kshта cshмасиви. Вони схожі, kshале зі значними відмінностями:

    • індекси починаються з 1, а не від 0 (за винятком kshемуляції), що відповідає масиву Bourne (параметри позиції $ @, який zshтакож виставляється як його масив $ argv) та cshмасивів.
    • вони є окремим типом від нормальних / скалярних змінних. Оператори ставляться до них по-різному, як і зазвичай ви очікували. $aне є тим самим, ${a[0]}але розширюється на непусті елементи масиву ( "${a[@]}"для всіх елементів, як у ksh).
    • вони є нормальними масивами, а не розрідженими масивами. a[5]=1працює, але призначає всі елементи від 1 до 4 порожнього рядка, якщо вони не були призначені. Отже ${#a[@]}(те саме, ${#a}що в ksh - розмір елемента індексу 0) - це кількість елементів у масиві та найбільший призначений індекс.
    • асоціативні масиви підтримуються.
    • підтримується велика кількість операторів, що працюють з масивами, занадто великі, щоб їх перелічити тут.
    • масиви, визначені як a=(x y). set -A a x yтакож працює, але set -A a -- x yне підтримується, якщо не в ksh емуляції (the --zsh emulation не потрібен).
  • ksh93. (тут описуються останні версії). ksh93, що довго вважається експериментальним, тепер можна знайти все більше і більше систем тепер, коли він був випущений як FOSS. Наприклад, це /bin/sh(там , де він замінив Bourne оболонки, /usr/xpg4/bin/sh, то POSIX оболонки по - , як і раніше грунтується на ksh88) і kshз Solaris 11. Її масиви розширюють та покращують ksh88.

    • a=(x y)може використовуватися для визначення масиву, але оскільки a=(...)він також використовується для визначення складних змінних ( a=(foo=bar bar=baz)), a=()є неоднозначним і оголошує складну змінну, а не масив.
    • масиви є багатовимірними ( a=((0 1) (0 2))), а елементи масиву також можуть бути складними змінними ( a=((a b) (c=d d=f)); echo "${a[1].c}").
    • a=([2]=foo [5]=bar)Синтаксис може бути використаний для визначення розріджених масивів відразу.
    • Скасовано обмеження розміру.
    • Не в міру zsh, але велика кількість операторів підтримує і маніпулювати масивами.
    • "${!a[@]}" для отримання списку індексів масиву.
    • асоціативні масиви також підтримуються як окремий тип.
  • bash. bashє оболонкою проекту GNU. Він використовується як і shв останніх версіях OS / X та деяких дистрибутивах GNU / Linux. bashмасиви здебільшого імітують ksh88ті, які мають деякі особливості ksh93та zsh.

    • a=(x y)підтримується. set -A a x y не підтримується. a=()створює порожній масив (в ньому немає складних змінних bash).
    • "${!a[@]}" для списку індексів.
    • a=([foo]=bar)підтримується синтаксис, а також декілька інших з ksh93та zsh.
    • останні bashверсії також підтримують асоціативні масиви як окремий тип.
  • yash. Це порівняно недавня, чиста, багатобайтна реалізація POSIX sh. Не широко використовується. Його масиви - це ще один чистий API, подібний доzsh

    • масиви не рідкісні
    • Індекси масиву починаються з 1
    • визначено (і оголошено) с a=(var value)
    • елементи, вставлені, видалені або модифіковані за допомогою arrayвбудованого
    • array -s a 5 valueзмінити 5- й елемент не вдалося б, якщо цей елемент не був призначений заздалегідь.
    • кількість елементів в масиві ${a[#]}, ${#a[@]}є розміром елементів у вигляді списку.
    • масиви - це окремий тип. Щоб a=("$a")додати або змінити елементи, вам потрібно переосмислити скалярну змінну як масив.
    • масиви не підтримуються, коли викликаються як sh.

Отже, з цього ви бачите те, що виявляє підтримку масиву, що ви можете зробити з:

if (unset a; set -A a a; eval "a=(a b)"; eval '[ -n "${a[1]}" ]'
   ) > /dev/null 2>&1
then
  array_supported=true
else
  array_supported=false
fi

недостатньо, щоб мати можливість використовувати ці масиви. Вам потрібно буде визначити команди обгортки для призначення масивів як цілого, так і окремих елементів, і переконайтеся, що ви не намагаєтеся створити розріджені масиви.

Подібно до

unset a
array_elements() { eval "REPLY=\"\${#$1[@]}\""; }
if (set -A a -- a) 2> /dev/null; then
  set -A a -- a b
  case ${a[0]}${a[1]} in
    --) set_array() { eval "shift; set -A $1"' "$@"'; }
        set_array_element() { eval "$1[1+(\$2)]=\$3"; }
        first_indice=0;;
     a) set_array() { eval "shift; set -A $1"' -- "$@"'; }
        set_array_element() { eval "$1[1+(\$2)]=\$3"; }
        first_indice=1;;
   --a) set_array() { eval "shift; set -A $1"' "$@"'; }
        set_array_element() { eval "$1[\$2]=\$3"; }
        first_indice=0;;
    ab) set_array() { eval "shift; set -A $1"' -- "$@"'; }
        set_array_element() { eval "$1[\$2]=\$3"; }
        first_indice=0;;
  esac
elif (eval 'a[5]=x') 2> /dev/null; then
  set_array() { eval "shift; $1=("'"$@")'; }
  set_array_element() { eval "$1[\$2]=\$3"; }
  first_indice=0
elif (eval 'a=(x) && array -s a 1 y && [ "${a[1]}" = y ]') 2> /dev/null; then
  set_array() { eval "shift; $1=("'"$@")'; }
  set_array_element() {
    eval "
      $1=(\${$1+\"\${$1[@]}"'"})
      while [ "$(($2))" -ge  "${'"$1"'[#]}" ]; do
        array -i "$1" "$2" ""
      done'
    array -s -- "$1" "$((1+$2))" "$3"
   }
  array_elements() { eval "REPLY=\${$1[#]}"; }
  first_indice=1
else
  echo >&2 "Array not supported"
fi

І ви отримаєте доступ до елементів масиву з "${a[$first_indice+n]}", весь список з "${a[@]}"і використовувати функції оболонки ( array_elements, set_array, set_array_element) , щоб отримати кількість елементів масиву (в $REPLY), встановіть масив в цілому або призначення окремих елементів.

Напевно, не варте зусиль. Я хотів би використовувати perlабо обмеження масиву оболонки Bourne / POSIX: "$@".

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

Ви можете налаштувати zshмасиви, схожі на kshмасиви в локальних областях (у функціях або анонімних функціях).

myfunction() {
  [ -z "$ZSH_VERSION" ] || setopt localoption ksharrays
  # use arrays of indice 0 in this function
}

Ви також можете емулювати ksh(покращувати сумісність з kshмасивами та кількома іншими областями) за допомогою:

myfunction() {
  [ -z "$ZSH_VERSION" ] || emulate -L ksh
  # ksh code more likely to work here
}

Маючи це на увазі, і ви готові відмовитися від підтримки yashі для ksh88старих версій pdkshпохідних, і поки ви не намагаєтеся створювати рідкісні масиви, ви маєте змогу послідовно використовувати:

  • a[0]=foo
  • a=(foo bar)(але ні a=())
  • "${a[#]}", "${a[@]}","${a[0]}"

в тих функціях, які мають emulate -L ksh, в той час як zshкористувач все ще використовує свої масиви, як правило, zsh-шлях.


7

Ви можете evalспробувати синтаксис масиву:

is_array_support() (
  eval 'a=(1)'
) >/dev/null 2>&1

if is_array_support; then
  echo support
else
  echo not
fi

2
ksh88підтримує масиви, але ні a=(). В ksh93, a=()оголошує змінну змінної, а не масив, якщо заздалегідь не була оголошена змінна як масив.
Стефан Шазелас

2
Також зауважте, що між реалізаціями масиву існують значні відмінності. Наприклад, деякі мають індекси масиву, починаючи з 0 (bash, ksh, zsh в ksh емуляції), деякі починаючи з одного (zsh, yash). Деякі - це звичайні масиви / списки, деякі - розріджені масиви (асоціативні масиви з ключами, обмеженими позитивними цілими числами, наприклад, у ksh або bash).
Стефан Шазелас

У yash, ви нічого не робите, a[5]=1алеarray -s a 5 1
Стефан Шазелас

@ StéphaneChazelas: дякую за вказівки. У моєму випадку все зводиться до того, чи підтримуються масиви (асоціативні чи ні). Деталі про базу індексів можна легко опрацювати навіть у сценарії, призначеному для запуску без нагляду.
Cbhihe

@ StéphaneChazelas: Складова змінна в ksh93мене здивувала, ви не хотіли б подати мені частину документації про це. Я додаю 1до масиву, щоб він працював.
cuonglm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.