Примечание
Я даю сильный ответ, ориентированный на Bash, из-за тега bash
.
Короткий ответ
Пока вы только имеете делос именованными переменными в Bash, эта функция всегда должна сообщать вам, была ли установлена переменная, даже если это пустой массив.
is-variable-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 sh, то вы не можете использовать массивы.Без массивов [ -n "{$var+x}" ]
работает.
Код сравнения для различных методов в Bash
Эта функция сбрасывает переменную var
, eval
s переданный код, запускает тесты для определения, если 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.