Примітка
Я даю сильно зосереджену відповідь Баша через 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 sh, ви не можете використовувати масиви. Без масивів [ -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
.