The following code exits with a unbound variable error. How can I fix this, while still using the set -o nounset option?
#!/bin/bash
set -o nounset
if [ ! -z ${WHATEVER} ];
then echo "yo"
fi
echo "whatever"
#!/bin/bash
set -o nounset
VALUE=${WHATEVER:-}
if [ ! -z ${VALUE} ];
then echo "yo"
fi
echo "whatever"
In this case, VALUE ends up being an empty string if WHATEVER is not set. We're using the {parameter:-word} expansion, which you can look up in man bash under "Parameter Expansion".
:- checks whether the variable is unset or empty. If you want to check only whether it's unset, use -: VALUE=${WHATEVER-}. Also, a more readable way to check whether a variable is empty: if [ "${WHATEVER+defined}" = defined ]; then echo defined; else echo undefined; fi$WHATEVER contains only whitespace - See my answer.! -z" instead of just "-n" ?You need to quote the variables if you want to get the result you expect:
check() {
if [ -n "${WHATEVER-}" ]
then
echo 'not empty'
elif [ "${WHATEVER+defined}" = defined ]
then
echo 'empty but defined'
else
echo 'unset'
fi
}
Test:
$ unset WHATEVER
$ check
unset
$ WHATEVER=
$ check
empty but defined
$ WHATEVER=' '
$ check
not empty
"info bash", "${WHATEVER-}" should have a ":" (colon) before the "-" (dash) like: "${WHATEVER:-}", and "${WHATEVER+defined}" should have a colon before the "+" (plus) like: "${WHATEVER:+defined}". For me, it works either way, with or without the colon. On some versions of 'nix it probably won't work without including the colon, so it should probably be added.-, +, :+, and :- are all supported. The former detect whether the variable is set, and the latter detect whether it is set or empty. From man bash: "Omitting the colon results in a test only for a parameter that is unset."Assumptions:
$ echo $SHELL
/bin/bash
$ /bin/bash --version | head -1
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
$ set -o nounset
If you want a non-interactive script to print an error and exit if a variable is null or not set:
$ [[ "${HOME:?}" ]]
$ [[ "${IAMUNBOUND:?}" ]]
bash: IAMUNBOUND: parameter null or not set
$ IAMNULL=""
$ [[ "${IAMNULL:?}" ]]
bash: IAMNULL: parameter null or not set
If you don't want the script to exit:
$ [[ "${HOME:-}" ]] || echo "Parameter null or not set."
$ [[ "${IAMUNBOUND:-}" ]] || echo "Parameter null or not set."
Parameter null or not set.
$ IAMNULL=""
$ [[ "${IAMUNNULL:-}" ]] || echo "Parameter null or not set."
Parameter null or not set.
You can even use [ and ] instead of [[ and ]] above, but the latter is preferable in Bash.
Note what the colon does above. From the documentation:
Put another way, if the colon is included, the operator tests for both parameter’s existence and that its value is not null; if the colon is omitted, the operator tests only for existence.
There is apparently no need for -n or -z.
In summary, I may typically just use [[ "${VAR:?}" ]]. Per the examples, this prints an error and exits if a variable is null or not set.
Use a oneliner:
[ -z "${VAR:-}" ] && echo "VAR is not set or is empty" || echo "VAR is set to $VAR"
-z checks both for empty or unset variable
-z only checks if the next parameter is empty. -z is is just an argument of the [ command. Variable expansion happens before [ -z can do anything.You can use
if [[ ${WHATEVER:+$WHATEVER} ]]; then
but
if [[ "${WHATEVER:+isset}" == "isset" ]]; then
might be more readable.
= operator, not == to aid in portability, and [ instead of [[ if possible.set -o nounset which is specific to bash. If you put a #!/bin/bash at the top of your script, it is actually best to use bash's enhancements.While this isn't exactly the use case asked for, I've found that if you want to use nounset (or -u) the default behavior is the one you want: to exit nonzero with a descriptive message.
If all you want is to echo something else when exiting, or do some cleanup, you can use a trap.
The :- operator is probably what you want otherwise.
To me, most of the answers are at best confusing, not including a test matrix. They also often do not address the scenario where variable contains the defaulting value.
The solution from l0b0 is the only readable, testable (and correct in respect to the actual question IMO), but it is unclear if inverting/reordering the tests to simplify the logic produces correct result. I minified hirs solution
The (already minified) contrast solution from Aleš, exposes the difference of a variable being declared but undefined. The one or the other might fit your scenario.
#!/bin/bash -eu
check1() {
if [[ -n "${WHATEVER-}" ]]; then
echo 'something else: not empty'
elif [[ "${WHATEVER+defined}" = defined ]]; then
echo 'something else: declared but undefined'
else
echo 'unset'
fi
}
check2() {
if [[ "${WHATEVER+defined}" != "defined" ]]; then
echo 'unset'
else
echo "something else"
fi
}
check3() {
if [[ "${WHATEVER-defined}" = defined ]]; then
echo 'unset'
else
echo 'something else'
fi
}
check4() {
if [[ ! ${WHATEVER+$WHATEVER} ]]; then
echo 'unset'
else
echo 'something else'
fi
}
echo
echo "check1 from l0b0"
unset WHATEVER
check1
WHATEVER=
check1
WHATEVER=' '
check1
WHATEVER='defined'
check1
echo
echo "check2 prove simplification keeps semantics"
unset WHATEVER
check2
WHATEVER=
check2
WHATEVER=' '
check2
WHATEVER='defined'
check2
echo
echo "check3 other promising operator?"
unset WHATEVER
check3
WHATEVER=
check3
WHATEVER=' '
check3
WHATEVER='defined'
check3
echo
echo "check4 other interesting suggestion, from aleš"
unset WHATEVER
check4
WHATEVER=
check4
WHATEVER=' '
check4
WHATEVER='defined'
check4
Check1 and Check2 behave identicallyCheck3 is plainly wrongCheck4: correct, depending on what you consider a declared/defined variable.check1 from l0b0
unset
something else: declared but undefined
something else: not empty
something else: not empty
check2 prove simplification keeps semantics
unset
something else
something else
something else
check3 other promising operator?
unset
something else
something else
unset
check4 other interesting suggestion, from aleš
unset
unset
something else
something else
This is my contribution bc once I began working with arrays/associative arrays, I needed an easy way to get the answer. My issue was: with 0 element arrays, this would return an error:
if [ ${#my_array[@]} -eq 0 ]; then #this will error if nounset is enabled!!
echo "return error due to empty array"
return 1
fi
var_stat() {
#Determines if a variable (or array) is unset, blank (or no keys) or populated (has key(s))
#Input: single parameter to be text of a variable name or an array or an associative array
#stdout depends on whether the input represents a variable (or array) that is:
# -1: unset
# 0: set and blank (or set with 0 keys)
# 1: set and not blank (or set and has key(s))
local input_var_name="${1:-"__NA"}"
if [[ "${input_var_name:-"__NA"}" = "__NA" ]]; then
echo -1
return 0
fi
#evaluate results of declare -p
case "$( declare -p ${input_var_name} 2>/dev/null || echo "__NA" )" in
#if begins with __NA then above command failed and variable is unset
__NA*)
echo -1
;;
*\))
#if ending with ) (escaped as '\)') then it is an array and populated
echo 1
;;
declare\ -[aA]*)
#if it begins with declare -a or declare -A it is an empty array (bc it failed the prior test)
echo 0
;;
#otherwise, it is a regular variable; z test is safe
*)
if [ -z "${!input_var_name}" ]; then #"${!input_var_name}" is indirect variable usage
#it is empty
echo 0
else
#it is not empty
echo 1
fi
esac
}
Unit Test:
#Usage - tested with both errexit/nounset on/off
#first, define a bunch of variables, some blank, some keyless, etc.
mysettext="settext"
myblanktext=""
declare -A myemptyAA
declare -a myemptya
declare -A myhaskeyAA
myhaskeyAA[oneElement]="associative array with one key"
declare -a myhaskeya
myhaskeya+=("array with one value")
declare -i myemptyinteger
declare -i mysetinteger
mysetinteger=3
#regular array to store the variable names of our tests
unset var_stat_test_array
declare -a var_stat_test_array
var_stat_test_array+=('mysettext')
var_stat_test_array+=('myblanktext')
var_stat_test_array+=('myemptyAA')
var_stat_test_array+=('myemptya')
var_stat_test_array+=('myhaskeyAA')
var_stat_test_array+=('myhaskeya')
var_stat_test_array+=('myemptyinteger')
var_stat_test_array+=('mysetinteger')
var_stat_test_array+=('myUNSET') #this is not set per above
#cycle through the test array, run declare -p then the var_stat function etc.
for eachElement in "${var_stat_test_array[@]}"; do
declare -p ${eachElement} || true #guarantee we succeed even if unset
var_stat "${eachElement}" | sed "s/^/ var_stat returned:/"
# echo " $? varstat outcome" #uncomment to verify 100% successes
echo "" #just formatting output
done
Output:
declare -- mysettext="settext"
var_stat returned:1
declare -- myblanktext=""
var_stat returned:0
declare -A myemptyAA
var_stat returned:0
declare -a myemptya
var_stat returned:0
declare -A myhaskeyAA=([oneElement]="associative array with one key" )
var_stat returned:1
declare -a myhaskeya=([0]="array with one value")
var_stat returned:1
declare -i myemptyinteger
var_stat returned:0
declare -i mysetinteger="3"
var_stat returned:1
-bash: declare: myUNSET: not found
var_stat returned:-1
Practical examples:
if [[ $( var_stat my_unset_var ) -eq -1 ]]; then
echo 'exit with error if this is a required variable; else safe to succeed'
return 1
fi
declare -a my_empty_array #or declare -A my_empty_array
if [[ $( var_stat my_empty_array ) -le 0 ]]; then
echo 'exit with error if an array (associative or not) must have at least one key/element; else safe to succeed'
return 1
fi