Как проверить, что переменная вообще определена в Bash до версии 4.2 с опцией оболочки nounset?

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, а оператор для условных выражений.
StackExchange для всех
@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-}для получения пустой строки, когда значение foobarundefined и значение foobarиначе (или поставить любое другое значение по умолчанию после -).

В ksh, если foobarобъявлен, но не определен, как в typeset -a foobar, то ${foobar+1}расширяется до пустой строки.

Zsh не имеет переменных, которые объявлены, но не установлены: typeset -a foobarсоздает пустой массив.

В bash массивы ведут себя по-другому и удивительно. ${a+1}расширяется только до 1if, если 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) -релиз (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». Если ваше определение отличается, bash немного странный. Смотрите мой обновленный ответ.
Жиль "ТАК - перестань быть злым"
1
Что касается темы «В bash массивы ведут себя по-другому и удивительно». поведение может быть объяснено из man-страниц bash (1), раздел «Массивы». В нем говорится, что «Ссылка на переменную массива без нижнего индекса эквивалентна ссылке на массив с нижним индексом 0.». Таким образом, если ни 0индекс, ни ключ не определены так, как это верно a=(), то ${a+1}ничего не возвращает правильно.
Тим Фриске
1
@TimFriske Я знаю, что реализация bash соответствует его документации. Но обработка пустого массива как неопределенной переменной - действительно странный дизайн.
Жиль "ТАК - перестань быть злым"
Я предпочитаю результат из: Как проверить, установлена ​​ли переменная в Bash? -> Правильный путь ... Я поражаюсь, как я думаю, это должно работать. Смею сказать, у Windows есть definedоператор. Test мог сделать это; это не может быть сложно ( гм ....)
будет
5

Подводя итог ответу Жиля, я сформулировал следующие правила:

  1. Используйте [[ -v foobar ]]для переменных в версии Bash> = 4.2.
  2. Используйте declare -p foobar &>/dev/nullдля переменных массива в версии Bash <4.2.
  3. Используйте (( ${foo[0]+1} ))или (( ${bar[foo]+1} ))для индексов массивов indexed ( -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)
Флориан Хейгл