Запись
Я даю сильно сфокусированный на 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
,