I'm giving a heavily Bash-focused answer because of the bash
tag.
As long as you're only dealing with named variables in Bash, this function should always tell you if the variable has been set, even if it's an empty array.
variable-is-set() {
declare -p "$1" &>/dev/null
}
In Bash (at least as far back as 3.0), if var
is a declared/set variable, then declare -p var
outputs a declare
command that would set variable var
to whatever its current type and value are, and returns status code 0
(success). If var
is undeclared, then declare -p var
outputs an error message to stderr
and returns status code 1
. Using &>/dev/null
, redirects both regular stdout
and stderr
output to /dev/null
, never to be seen, and without changing the status code. Thus the function only returns the status code.
[ -n "$var" ]
: This only checks if${var[0]}
is nonempty. (In Bash,$var
is the same as${var[0]}
.)[ -n "${var+x}" ]
: This only checks if${var[0]}
is set.[ "${#var[@]}" != 0 ]
: This only checks if at least one index of$var
is set.
This only works for named variables (including $_
), not certain special variables ($!
, $@
, $#
, $$
, $*
, $?
, $-
, $0
, $1
, $2
, ..., and any I may have forgotten). Since none of these are arrays, the POSIX-style [ -n "${var+x}" ]
works for all of these special variables. But beware of wrapping it in a function since many special variables change values/existence when functions are called.
If your script has arrays and you're trying to make it compatible with as many shells as possible, then consider using typeset -p
instead of declare -p
. I've read that ksh only supports the former, but haven't been able to test this. I do know that Bash 3.0+ and Zsh 5.5.1 each support both typeset -p
and declare -p
, differing only in which one is an alternative for the other. But I haven't tested differences beyond those two keywords, and I haven't tested other shells.
If you need your script to be POSIX sh compatible, then you can't use arrays. Without arrays, [ -n "{$var+x}" ]
works.
This function unsets variable var
, eval
s the passed code, runs tests to determine if var
is set by the eval
d code, and finally shows the resulting status codes for the different tests.
I'm skipping test -v var
, [ -v var ]
, and [[ -v var ]]
because they yield identical results to the POSIX standard [ -n "${var+x}" ]
, while requiring Bash 4.2+. I'm also skipping typeset -p
because it's the same as declare -p
in the shells I've tested (Bash 3.0 thru 5.0, and 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
}
Note that test results may be unexpected due to Bash treating non-numeric array indices as "0" if the variable hasn't been declared as an associative array. Also, associative arrays are only valid in 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
The test mnemonics in the header row correspond to [ -n "$var" ]
, [ -n "${var+x}" ]
, [ "${#var[@]}" != 0 ]
, and declare -p var
, respectively.
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
is (100%?) reliable for testing named variables in Bash since at least 3.0.[ -n "${var+x}" ]
is reliable in POSIX compliant situations, but cannot handle arrays.- Other tests exist for checking if a variable is nonempty, and for checking for declared variables in other shells. But these tests are suited for neither Bash nor POSIX scripts.