Intereting Posts

Проверить переменную – массив в Bourne как shell?

В Bourne, таком как shell, который поддерживает переменную массива, мы можем использовать некоторый синтаксический анализ, чтобы проверить, является ли переменная массивом.

Все команды ниже выполнялись после запуска a=(1 2 3) .

zsh :

 $ declare -pa typeset -aa a=( 1 2 3 ) 

bash :

 $ declare -pa declare -aa='([0]="1" [1]="2" [2]="3")' 

ksh93 :

 $ typeset -pa typeset -aa=(1 2 3) 

pdksh и его производные:

 $ typeset -pa set -A a typeset a[0]=1 typeset a[1]=2 typeset a[2]=3 

yash :

 $ typeset -pa a=('1' '2' '3') typeset a 

Пример в bash :

 if declare -p var 2>/dev/null | grep -q 'declare -a'; then echo array variable fi 

Этот подход – слишком большая работа, и нужно порождать подоболочку. Использование другой оболочки, такой как =~ в [[ ... ]] , не нуждается в подоболочке, но все еще слишком сложна.

Есть ли более простой способ выполнить эту задачу?

Я не думаю, что могу, и я не думаю, что это действительно имеет значение.

 unset a a=x echo "${a[0]-not array}" 

 x 

Это делает то же самое в любом из ksh93 и bash . Похоже, что все переменные являются массивами в этих оболочках или, по крайней мере, любой регулярной переменной, которой не были назначены специальные атрибуты, но я не проверял многое из этого.

В руководстве bash рассказывается о разных поведении для массива по сравнению с строковой переменной при использовании += присваиваний, но впоследствии хеджируется и заявляет, что массив ведет себя по-разному в контексте составного назначения.

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

Практически, возможно, вы можете использовать:

 [ 1 = "${a[0]+${#a[@]}}" ] && echo not array 

… чтобы четко определить заданные переменные, которым был присвоен только один индекс значения 0.

Значит, вы действительно хотите получить только среднюю часть declare -p без мусора?

Вы можете написать макрос, например:

 readonly VARTYPE='{ read __; case "`declare -p "$__"`" in "declare -a"*) echo array;; "declare -A"*) echo hash;; "declare -- "*) echo scalar;; esac; } <<<' 

так что вы можете сделать:

 a=scalar b=( array ) declare -A c; c[hashKey]=hashValue; ###################################### eval "$VARTYPE" a #scalar eval "$VARTYPE" b #array eval "$VARTYPE" c #hash значение a=scalar b=( array ) declare -A c; c[hashKey]=hashValue; ###################################### eval "$VARTYPE" a #scalar eval "$VARTYPE" b #array eval "$VARTYPE" c #hash 

(Простая функция не будет работать, если вы захотите использовать ее для локальных переменных-функций).


С псевдонимами

 shopt -s expand_aliases alias vartype='eval "$VARTYPE"' vartype a #scalar vartype b #array vartype c #hash 

В zsh

 zsh% a=(1 2 3) s=1 zsh% [[ ${(t)a} == *array* ]] && echo array array zsh% [[ ${(t)s} == *array* ]] && echo array zsh% 

Чтобы проверить переменную var, с

 b=("${!var[@]}") c="${#b[@]}" 

Можно проверить, существует ли более одного индекса массива:

 [[ $c > 1 ]] && echo "Var is an array" 

Если первое значение индекса не равно нулю:

 [[ ${b[0]} -eq 0 ]] && echo "Var is an array" ## should be 1 for zsh. 

Единственное затруднение состоит в том, что есть только одно значение индекса, и это значение равно нулю (или одному).

Для этого условия можно использовать побочный эффект попытки удалить элемент массива из переменной, которая не является массивом:

 **bash** reports an error with unset var[0] bash: unset: var: not an array variable **zsh** also reports an error with $ var[1]=() attempt to assign array value to non-array 

Это правильно работает для bash:

 # Test if the value at index 0 could be unset. # If it fails, the variable is not an array. ( unset "var[0]" 2>/dev/null; ) && echo "var is an array." 

Для zsh индекс может быть равен 1 (если активен не совместимый режим).

Под-оболочка необходима, чтобы избежать побочного эффекта стирания индекса 0 из var.

Я не нашел способа заставить его работать в ksh.

Редактировать 1

Эта функция работает только в bash4.2 +

 getVarType(){ varname=$1; case "$(typeset -p "$varname")" in "declare -a"*|"typeset -a"*) echo array; ;; "declare -A"*|"typeset -A"*) echo hash; ;; "declare -- "*|"typeset "$varname*| $varname=*) echo scalar; ;; esac; } var=( foo bar ); getVarType var 

Изменить 2

Это также работает только для bash4.2 +

 { typeset -p var | grep -qP '(declare|typeset) -a'; } && echo "var is an array" 

Примечание. Это даст ложные срабатывания, если var содержит проверенные строки.

Для bash это немного взломанный (хотя и документированный): попытка использовать typeset для удаления атрибута «array»:

 $ typeset +a BASH_VERSINFO bash: typeset: BASH_VERSINFO: cannot destroy array variables in this way echo $? 1 

(Вы не можете сделать это в zsh , это позволяет преобразовать массив в скаляр, в bash это явно запрещено).

Так:

  typeset +A myvariable 2>/dev/null || echo is assoc-array typeset +a myvariable 2>/dev/null || echo is array 

Или в функции, отмечая оговорки в конце:

 function typeof() { local _myvar="$1" if ! typeset -p $_myvar 2>/dev/null ; then echo no-such elif ! typeset -g +A $_myvar 2>/dev/null ; then echo is-assoc-array elif ! typeset -g +a $_myvar 2>/dev/null; then echo is-array else echo scalar fi } 

Обратите внимание на использование typeset -g (bash-4.2 или новее), это требуется внутри функции, так что typeset (syn. declare ) не работает как local и clobber значение, которое вы пытаетесь проверить. Это также не относится к функциям «переменных» типов, вы можете добавить еще один тест ветви с помощью typeset -f если это необходимо.


Другой (почти полный) вариант заключается в том, чтобы использовать это:

  ${!name[*]} If name is an array variable, expands to the list of array indices (keys) assigned in name. If name is not an array, expands to 0 if name is set and null otherwise. When @ is used and the expansion appears within double quotes, each key expands to a separate word. 

Однако есть одна небольшая проблема: массив с одним индексом 0 соответствует двум из вышеперечисленных условий. Это то, что mikeserv также ссылается, bash действительно не имеет жесткого различия, и некоторые из них (если вы проверяете Changelog) можно обвинить в ksh и совместимости с тем, как ${name[*]} или ${name[@]} ведут себя не-массив.

Таким образом, частичное решение:

 if [[ ${!BASH_VERSINFO[*]} == '' ]]; then echo no-such elif [[ ${!BASH_VERSINFO[*]} == '0' ]]; then echo not-array elif [[ ${!BASH_VERSINFO[*]} != '0' ]]; echo is-array fi 

В прошлом я использовал вариацию:

 while read _line; do if [[ $_line =~ ^"declare -a" ]]; then ... fi done < <( declare -p ) 

это тоже требует подоболочки.

Еще один, возможно, полезный метод: compgen :

 compgen -A arrayvar 

Это будет перечислять все индексированные массивы, однако ассоциативные массивы не обрабатываются специально (вплоть до bash-4.4) и отображаются как обычные переменные (переменная compgen -A variable )

Встроенный array yash имеет некоторые параметры, которые работают только с переменными массива. Пример: опция -d сообщит об ошибке для переменной без массива:

 $ a=123 $ array -da array: no such array $a 

Поэтому мы можем сделать что-то вроде этого:

 is_array() ( array -d -- "$1" ) >/dev/null 2>&1 a=(1 2 3) if is_array a; then echo array fi b=123 if ! is_array b; then echo not array fi 

Этот подход не будет работать, если переменная массива только для чтения . Попытка изменить переменную readonly, приводящую к ошибке:

 $ a=() $ readonly a $ array -da array: $a is read-only 

Короткий ответ:

Для двух оболочек, которые ввели это обозначение ( bash и ksh93 ), скалярная переменная представляет собой просто массив с одним элементом .

Для создания массива также не требуется специальное объявление. Просто присваивания достаточно, и простое var=value идентично var=value var[0]=value .