Перевірте, чи змінний масив у Bourne як оболонка?


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

Такий підхід є занадто великою роботою і потребує нересту. Використання іншої вбудованої оболонки, як =~у [[ ... ]], не потребує вкладеній оболонці , але все ще занадто складна.

Чи є простіший спосіб виконати це завдання?


За яких обставин вам потрібно перевірити, чи є ваші змінні масивами чи ні?
Kusalananda

Відповіді:


10

Я не думаю, що ти можеш, і я не думаю, що це насправді має значення.

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

x

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

У bashпосібнику йдеться про різні способи поведінки для масиву проти рядкової змінної при використанні +=призначень, але після цього хеджує і заявляє, що масив поводиться по-різному лише в контексті складеного призначення.

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

Практично, можливо, ви можете використовувати:

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

... чітко визначити набір змінних, яким присвоєно лише один індекс під значенням 0.


Тож я гадаю, перевіряючи, чи ${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

@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%

Можливо 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
Редагування дат, тхо. Виглядає дуже оригінально. : D
PSkocik

@cuonglm Перевірка ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."правильно повідомляє var - це масив, коли var був встановлений для var=()масиву з нульовими елементами. Він діє рівно, щоб оголосити.

Тест на скаляр не працюватиме, якщо скаляр експортується або позначений цілим / малим / маленьким / лише для читання ... Ви, ймовірно, можете впевнено зробити так, що будь-який інший не порожній вихід означає скалярну змінну. Я б використовував grep -Eзамість цього, grep -Pщоб уникнути залежності від грепу GNU.
Стефан Шазелас

@ StéphaneChazelas Випробування (в БАШЕЄВ) для скалярного з цілими числами і / або нижнім регістром і / або тільки для читання завжди починається з -a, як це: declare -airl var='()'. Тому греп-тест спрацює .

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(синх. 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)


typeset +aТакож повідомляє про помилку в KSH. Не в zsh, хоча.

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'. Чому є різниця ?. Відповідь: Оболонка підтримує (для хорошого чи поганого) поняття про те, які вари є скалярами чи масивами. Оболонка ksh поєднує обидва поняття в одне ціле.

1

Яша в arrayвбудованому мають кілька варіантів , що тільки робота зі змінним масивом. Приклад: -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

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.