Запись
Я даю сильно сфокусированный на Bash ответ из-за bashтега.
Короткий ответ
Пока вы имеете дело только с именованными переменными в Bash, эта функция всегда должна сообщать вам, установлена ли переменная, даже если это пустой массив.
variable-is-set() {
declare -p "$1" &>/dev/null
}
Почему это работает
В Bash (по крайней мере, начиная с версии 3.0), если varпеременная объявлена / установлена, declare -p varвыводит declareкоманду, которая установит переменную varв соответствии с ее текущим типом и значением, и возвращает код состояния 0(успех). Если значение varне объявлено, declare -p varвыводится сообщение об ошибке stderrи возвращается код состояния 1. Используя &>/dev/null, перенаправляет как обычный, так stdoutи stderrвыходной /dev/null, никогда не будет видно и без изменения кода состояния. Таким образом, функция возвращает только код состояния.
Почему другие методы (иногда) терпят неудачу в Bash
[ -n "$var" ]: Только проверяет, если ${var[0]}не пусто. (В Bash так $varже, как ${var[0]}.)
[ -n "${var+x}" ]: Это только проверяет, если ${var[0]}установлено.
[ "${#var[@]}" != 0 ]: Это только проверяет, установлен ли хотя бы один индекс $var.
Когда этот метод не работает в Bash
Это работает только для именованных переменных ( в том числе $_), а не некоторых специальных переменных ( $!, $@, $#, $$, $*, $?, $-, $0, $1, $2, ..., и любой я забыл). Поскольку ни один из них не является массивом, стиль POSIX [ -n "${var+x}" ]работает для всех этих специальных переменных. Но остерегайтесь оборачивать его в функцию, так как многие специальные переменные изменяют значения / существование при вызове функций.
Примечание о совместимости оболочки
Если в вашем скрипте есть массивы и вы пытаетесь сделать его совместимым с как можно большим количеством оболочек, рассмотрите возможность использования typeset -pвместо declare -p. Я читал, что ksh поддерживает только первое, но не смог проверить это. Я знаю, что Bash 3.0+ и Zsh 5.5.1 каждый поддерживает оба typeset -pи declare -pотличается только тем, что один является альтернативой для другого. Но я не проверял различия между этими двумя ключевыми словами, и я не проверял другие оболочки.
Если вам нужен ваш POSIX-совместимый скрипт, то вы не можете использовать массивы. Без массивов [ -n "{$var+x}" ]работает.
Код сравнения для разных методов в Bash
Эта функция сбрасывает переменную var, evalпередает переданный код, запускает тесты, чтобы определить, varустановлен ли evalкод d, и, наконец, показывает результирующие коды состояния для различных тестов.
Я пропускаю test -v var, [ -v var ]и [[ -v var ]]потому что они дают результаты, идентичные стандарту POSIX [ -n "${var+x}" ], при этом требуется Bash 4.2+. Я также пропускаю, typeset -pпотому что это так же, как declare -pв оболочках, которые я тестировал (Bash 3.0 через 5.0 и Zsh 5.5.1).
is-var-set-after() {
# Set var by passed expression.
unset var
eval "$1"
# Run the tests, in increasing order of accuracy.
[ -n "$var" ] # (index 0 of) var is nonempty
nonempty=$?
[ -n "${var+x}" ] # (index 0 of) var is set, maybe empty
plus=$?
[ "${#var[@]}" != 0 ] # var has at least one index set, maybe empty
count=$?
declare -p var &>/dev/null # var has been declared (any type)
declared=$?
# Show test results.
printf '%30s: %2s %2s %2s %2s\n' "$1" $nonempty $plus $count $declared
}
Код теста
Обратите внимание, что результаты теста могут быть неожиданными из-за того, что Bash рассматривает индексы нечислового массива как «0», если переменная не была объявлена как ассоциативный массив. Кроме того, ассоциативные массивы действительны только в Bash 4.0+.
# Header.
printf '%30s: %2s %2s %2s %2s\n' "test" '-n' '+x' '#@' '-p'
# First 5 tests: Equivalent to setting 'var=foo' because index 0 of an
# indexed array is also the nonindexed value, and non-numerical
# indices in an array not declared as associative are the same as
# index 0.
is-var-set-after "var=foo" # 0 0 0 0
is-var-set-after "var=(foo)" # 0 0 0 0
is-var-set-after "var=([0]=foo)" # 0 0 0 0
is-var-set-after "var=([x]=foo)" # 0 0 0 0
is-var-set-after "var=([y]=bar [x]=foo)" # 0 0 0 0
# '[ -n "$var" ]' fails when var is empty.
is-var-set-after "var=''" # 1 0 0 0
is-var-set-after "var=([0]='')" # 1 0 0 0
# Indices other than 0 are not detected by '[ -n "$var" ]' or by
# '[ -n "${var+x}" ]'.
is-var-set-after "var=([1]='')" # 1 1 0 0
is-var-set-after "var=([1]=foo)" # 1 1 0 0
is-var-set-after "declare -A var; var=([x]=foo)" # 1 1 0 0
# Empty arrays are only detected by 'declare -p'.
is-var-set-after "var=()" # 1 1 1 0
is-var-set-after "declare -a var" # 1 1 1 0
is-var-set-after "declare -A var" # 1 1 1 0
# If 'var' is unset, then it even fails the 'declare -p var' test.
is-var-set-after "unset var" # 1 1 1 1
Тестовый вывод
Испытуемые Мнемоники в заголовке строки соответствуют [ -n "$var" ], [ -n "${var+x}" ], [ "${#var[@]}" != 0 ], и declare -p var, соответственно.
test: -n +x #@ -p
var=foo: 0 0 0 0
var=(foo): 0 0 0 0
var=([0]=foo): 0 0 0 0
var=([x]=foo): 0 0 0 0
var=([y]=bar [x]=foo): 0 0 0 0
var='': 1 0 0 0
var=([0]=''): 1 0 0 0
var=([1]=''): 1 1 0 0
var=([1]=foo): 1 1 0 0
declare -A var; var=([x]=foo): 1 1 0 0
var=(): 1 1 1 0
declare -a var: 1 1 1 0
declare -A var: 1 1 1 0
unset var: 1 1 1 1
Резюме
declare -p var &>/dev/null (100%?) надежен для тестирования именованных переменных в Bash, начиная с версии 3.0.
[ -n "${var+x}" ] надежен в POSIX-совместимых ситуациях, но не может обрабатывать массивы.
- Существуют другие тесты для проверки, является ли переменная непустой, и для проверки объявленных переменных в других оболочках. Но эти тесты не подходят ни для скриптов Bash, ни для POSIX.
if test $# -gt 0; then printf 'arg <%s>\n' "$@"; fi,