Як перевірити, чи визначена змінна взагалі в Bash до версії 4.2 з опцією оболонки іменника?


16

Для версій Bash до "GNU bash, версія 4.2" чи є еквівалентні альтернативи для -vпараметра testкоманди? Наприклад:

shopt -os nounset
test -v foobar && echo foo || echo bar
# Output: bar
foobar=
test -v foobar && echo foo || echo bar
# Output: foo

-vне варіант для test, але оператор для умовних виразів.
Тім

@Tim Це три речі, окрім того, що це маркер, рядок та частина рядка: optionкоманда test -v, а operatorдо conditional expressionі unary test primaryдля [ ]. Не змішуйте англійську мову з визначеннями оболонок.

Відповіді:


36

Переноситься на всі оболонки POSIX:

if [ -n "${foobar+1}" ]; then
  echo "foobar is defined"
else
  echo "foobar is not defined"
fi

Зробіть це, ${foobar:+1}якщо ви хочете поводитись foobarтак само, будь то порожній чи не визначений. Ви також ${foobar-}можете отримати порожній рядок, коли він foobarне визначений, а значення foobarіншим чином (або поставити будь-яке інше значення за замовчуванням після -).

У ksh, якщо foobarоголошено, але не визначено, як у typeset -a foobar, то ${foobar+1}розширюється на порожній рядок.

Zsh не має змінних, які оголошуються, але не встановлюються: typeset -a foobarстворює порожній масив.

У bash масиви поводяться по-різному і дивно. ${a+1}тільки розширюється, 1якщо aце не порожній масив, наприклад ,

typeset -a a; echo ${a+1}    # prints nothing
e=(); echo ${e+1}            # prints nothing!
f=(''); echo ${f+1}          # prints 1

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

Інший, специфічний для bash спосіб тестування того, чи була визначена будь-яка змінна будь-якого типу, - це перевірити, чи є вона в списку . Це звітує про порожні масиви, як визначено, на відміну від них , але звітує про оголошені, але непризначені змінні (${!PREFIX*}${foobar+1}unset foobar; typeset -a foobar ) як невизначені.

case " ${!foobar*} " in
  *" foobar "*) echo "foobar is defined";;
  *) echo "foobar is not defined";;
esac

Це еквівалентно тестуванню повернутого значення typeset -p foobarабоdeclare -p foobar , за винятком того, що typeset -p foobarне вдається на оголошених, але непризначених змінних.

У bash, як у ksh, set -o nounset; typeset -a foobar; echo $foobar запускається помилка при спробі розширення невизначеної змінної foobar. На відміну від ksh, set -o nounset; foobar=(); echo $foobar(або echo "${foobar[@]}") також викликає помилку.

Зауважте, що в усіх описаних тут ситуаціях ${foobar+1}розширюється на порожній рядок, якщо і лише тоді, якщо $foobarце призведе до помилки під set -o nounset.


1
А як з масивами? У версії Bash "GNU bash, версія 4.1.10 (4) -release (i686-pc-cygwin)" echo "${foobar:+1}"не друкується, 1якщо declare -a foobarраніше було видано, і таким чином foobarє індексованим масивом. declare -p foobarправильно звітує declare -a foobar='()'. Чи "${foobar:+1}"працює лише для змінних без масиву?
Тім Фріске

@TimFriske ${foobar+1}(без того :, я перевернув два приклади в моїй оригінальній відповіді) є правильним для масивів у bash, якщо ваше визначення "визначеного" є "буде $foobarпрацювати під set -o nounset". Якщо ваше визначення інше, баш трохи дивний. Дивіться мою оновлену відповідь.
Жил "ТАК - перестань бути злим"

1
Що стосується теми "In bash, масиви поводяться по-іншому та дивно". поведінку можна пояснити з наборів (1) bash (1), розділ "Масиви". У ньому йдеться про те, що "Посилання на змінну масиву без індексів еквівалентно посиланню масиву на підпис з 0.". Таким чином, якщо ні 0індекс, ні ключ не визначені так, як це правда a=(), ${a+1}правильно нічого не повертає.
Тім Фріске

1
@TimFriske Я знаю, що реалізація bash відповідає його документації. Але поводження з порожнім масивом як невизначеною змінною - це дійсно дивна конструкція.
Жил "ТАК - перестань бути злим"

Я вважаю за краще результат від: Як перевірити, чи вказана змінна в Bash? -> Правильний шлях ... Я вражаю акордом того, як я на цьому повинен працювати. Смію сказати, у Windows є definedоператор. Test міг би це зробити; вона не може бути важко ( гм ....)
буде

6

Підводячи підсумки відповіді Жиля, я склав свої наступні правила:

  1. Використовуйте [[ -v foobar ]] для змінних у версії Bash> = 4.2.
  2. Використовуйте declare -p foobar &>/dev/null для змінних масивів у версії Bash <4.2.
  3. Використовуйте (( ${foo[0]+1} ))або (( ${bar[foo]+1} ))для підписок індексованих ( -a) та keyed ( -A) масивів ( declare) відповідно. Тут не працюють варіанти 1 і 2.

3

Я використовую однакову техніку для всіх змінних у bash, і вона працює, наприклад:

[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

Виходи:

foobar is unset

в той час як

foobar=( "val" "val2" )
[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

Виходи:

foobar is set

Довелося видалити [@], якщо масив має більше одного значення.
Штейн Інге Морісбак

1
Це чудово працює, якщо ви хочете перевірити, чи має воно значення, а не те, чи визначено воно. Тобто, foobar=""повідомлять про це foobar is unset. Не чекайте, я повертаю це назад. Дійсно тестує лише, чи перший елемент порожній чи ні, тому здається, що це лише гарна ідея, якщо ви знаєте, що змінна НЕ є масивом, і вас цікавить лише порожнеча, а не визначеність.
Рон Берк

працює лише в тому випадку, якщо ваші сценарії працюють із невизначеними змінними, дозволеними (без набору -u)
Флоріан Хейгл,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.