Проверьте переменную, является ли массив в Борне подобным оболочке?

14

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

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

zsh:

$ declare -p a
typeset -a a
a=( 1 2 3 )

bash:

$ declare -p a
declare -a a='([0]="1" [1]="2" [2]="3")'

ksh93:

$ typeset -p a
typeset -a a=(1 2 3)

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

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

yash:

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

Пример в bash:

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

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

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

cuonglm
источник
При каких обстоятельствах вам нужно проверить, являются ли ваши переменные массивами или нет?
Кусалананда

Ответы:

10

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

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

x

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

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

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

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

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

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

mikeserv
источник
Так что я думаю, проверка, ${a[1]-not array}может ли сделать задачу, не так ли?
Cuonglm
@cuonglm - Ну, не в соответствии с bashруководством: переменная массива считается установленной, если подстрочный индекс был присвоен значение. Нулевая строка является допустимым значением. Если какой-либо нижний индекс назначен, его массив для спецификации. На практике также нет, потому что вы можете сделать a[5]=x. Я думаю, [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]может сработать.
mikeserv
6

Таким образом, вы действительно хотите только среднюю часть 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

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


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

shopt -s expand_aliases
alias vartype='eval "$VARTYPE"'

vartype a #scalar
vartype b #array
vartype c #hash
PSkocik
источник
@mikeserv Хороший вопрос. Псевдонимы делают его красивее. +1
PSkocik
я имел в виду - alias vartype="$VARTYPE"... или просто не определял $VARTYPEвообще - это должно работать, верно? вам нужна только эта shoptвещь, bashпотому что она не соответствует спецификации aliasрасширения в скриптах.
mikeserv
1
@mikeserv Я уверен, что cuonglm вполне способен настроить этот подход к своим потребностям и предпочтениям. ;-)
PSkocik
... и соображения безопасности.
PSkocik
Ни в коем случае вышеприведенный код не предоставляет пользователю текст. Это не менее безопасно, чем функция. Я никогда не видел, чтобы вы беспокоились о том, чтобы сделать функции доступными только для чтения, но хорошо, я могу пометить переменную только для чтения.
PSkocik
6

В зш

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%
llua
источник
Может быть echo ${(t)var}, проще. Спасибо за это.
4

Чтобы проверить переменную 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 содержит проверенные строки.


источник
Как насчет массива с нулевыми элементами?
Cuonglm
1
Дата редактирования, tho. Выглядит очень оригинально. : D
PSkocik
@cuonglm Проверка ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."правильно сообщает, что var - это массив, если переменная установлена ​​в var=()массив с нулевыми элементами. Это действует точно так же, как объявить.
Тест для скаляра не будет работать, если скаляр экспортирован или помечен как целое число / нижний регистр / только для чтения ... Вы можете, вероятно, сделать так, чтобы любой другой непустой вывод означал скалярную переменную. Я бы использовал grep -Eвместо того, grep -Pчтобы избежать зависимости от GNU grep.
Стефан Шазелас
@ StéphaneChazelas Испытания (в Баше) для скалярного с целыми числами и / или нижним регистром и / или только для чтения всегда начинается с -a, как это: declare -airl var='()'. Поэтому тест grep будет работать .
3

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

$ 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и не затирало значение, которое вы пытаетесь проверить. Это также не обрабатывает «переменные» типы функций, вы можете добавить другой тест ветвления, используя 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 на самом деле не имеет жестких различий, и некоторые из них (если вы посмотрите журнал изменений) можно обвинить в 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).

mr.spuratic
источник
typeset +aТакже сообщает об ошибке в KSH. Не в зш, хотя.
1

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

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

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

Хенк Лангевельд
источник
Попробуйте: bash -c 'unset var; var=foo; typeset -p var'. Ответ bash сообщает о массиве (нужен -a)? Теперь сравните с: bash -c 'unset var; var[12]=foo; typeset -p var'. Почему есть разница? A: Оболочка поддерживает (хорошо или плохо) представление о том, какие переменные являются скалярами или массивами. Оболочка ksh смешивает обе концепции в одну.
1

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

$ a=123
$ array -d a
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

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

$ a=()
$ readonly a
$ array -d a
array: $a is read-only
cuonglm
источник
0
#!/bin/bash

var=BASH_SOURCE

[[ "$(declare -pa)" =~ [^[:alpha:]]$var= ]]

case "$?" in 
  0)
      echo "$var is an array variable"
      ;;
  1)
      echo "$var is not an array variable"
      ;;
  *)
      echo "Unknown exit code"
      ;;
esac
Fólkvangr
источник