Проверка поддержки массивов оболочкой

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 , например .)

Я ищу простой тест, который можно было бы оставить без присмотра и включить в раздел « Использование » в начале сценария или даже перед его вызовом.

Cbhihe
источник
Я полагаю, вы хотите ограничить борновские оболочки?
Стефан Шазелас
@ 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в большинстве традиционных коммерческих Unix-систем, где он также является основой 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эмуляции), что согласуется с массивом Борна (параметры позиции $ @, который 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 ( --в эмуляции zsh не требуется).
  • ksh93, (здесь описаны последние версии). ksh93долгое время считавшийся экспериментальным, теперь можно найти во все большем количестве систем теперь, когда он выпущен как FOSS. Например, это /bin/sh(где он заменил оболочку Bourne, оболочка /usr/xpg4/bin/shPOSIX по-прежнему основана ksh88) и kshof 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
cuonglm
источник
2
ksh88поддерживает массивы, но нет a=(). В ksh93, a=()объявляет составную переменную, а не массив, если переменная не была объявлена ​​как массив заранее.
Стефан Шазелас
2
Также обратите внимание, что между реализациями массивов есть существенные различия. Например, некоторые имеют индексы массива, начинающиеся с 0 (bash, ksh, zsh в эмуляции ksh), некоторые начинаются с одного (zsh, yash). Некоторые являются обычными массивами / списками, некоторые - разреженными (ассоциативные массивы с ключами, ограниченными положительными целыми числами, как в ksh или bash).
Стефан Шазелас
В yash, вы не делаете, a[5]=1ноarray -s a 5 1
Стефан Chazelas
@ StéphaneChazelas: спасибо за точность. В моем случае все сводится к тому, поддерживаются ли массивы (ассоциативные или нет) вообще. Подробности о index-base могут быть легко разработаны даже в сценарии, предназначенном для запуска без присмотра.
Cbhihe
@ StéphaneChazelas: Составная переменная в ksh93меня удивила, не могли бы вы дать мне часть документации об этом. Я добавляю 1в массив, чтобы он работал.
cuonglm