'Posix shell: distinguish between empty and not existing variable
In pure /bin/sh
how can I distinguish between an empty variable, an unset variable and a not existing (not defined) variable.
Here are the case:
# Case 1: not existing
echo "${foo}"
# Case 2: unset
foo=
echo "${foo}"
# Case 3: Empty
foo=""
echo "${foo}"
Now I would like to check for each of those three cases. If case 2 and case 3 are actually the same, then I must at least be able to distinguish between them and case 1.
Any idea?
UPDATE Solved thanks to Matteo
Here is how the code looks like:
#foo <-- not defined
bar1=
bar2=""
bar3="a"
if ! set | grep '^foo=' >/dev/null 2>&1; then
echo "foo does not exist"
elif [ -z "${foo}" ]; then
echo "foo is empty"
else
echo "foo has a value"
fi
if ! set | grep '^bar1=' >/dev/null 2>&1; then
echo "bar1 does not exist"
elif [ -z "${bar1}" ]; then
echo "bar1 is empty"
else
echo "bar1 has a value"
fi
if ! set | grep '^bar2=' >/dev/null 2>&1; then
echo "bar2 does not exist"
elif [ -z "${bar2}" ]; then
echo "bar2 is empty"
else
echo "bar2 has a value"
fi
if ! set | grep '^bar3=' >/dev/null 2>&1; then
echo "bar3 does not exist"
elif [ -z "${bar3}" ]; then
echo "bar3 is empty"
else
echo "bar3 has a value"
fi
And the results:
foo does not exist
bar1 is empty
bar2 is empty
bar3 has a value
Solution 1:[1]
You can use set
If no options or arguments are specified, set shall write the names and values of all shell variables in the collation sequence of the current locale. Each name shall start on a separate line, using the format:
You can list all the variables (set
) and grep for the variable name you want to check
set | grep '^foo='
Solution 2:[2]
I dont know about sh
, but in bash
and dash
you can do echo ${TEST:?Error}
for case 1 vs. case 2/3. And from quick glance at wikibooks, it seems like it should work for bourne shell too.
You can use it like this in bash and dash (use $? to get the error code)
echo ${TEST:?"Error"}
bash: TEST: Error
[lf@dell:~/tmp/soTest] echo $?
1
[lf@dell:~/tmp/soTest] TEST2="ok"
[lf@dell:~/tmp/soTest] echo ${TEST2:?"Error"}
ok
[lf@dell:~/tmp/soTest] echo $?
0
[lf@dell:~/tmp/soTest] dash
$ echo ${TEST3:?"Error"}
dash: 1: TEST3: Error
$ TEST3=ok
$ echo ${TEST3:?"Error"}
ok
Solution 3:[3]
You can use ${var?} syntax to throw an error if var is unset and ${var:?} to throw an error if var is unset or empty. For a concrete example:
$ unset foo
$ test -z "${foo?unset}" && echo foo is empty || echo foo is set to $foo
-bash: foo: unset
$ foo=
$ test -z "${foo?unset}" && echo foo is empty || echo foo is set to $foo
foo is empty
$ foo=bar
$ test -z "${foo?unset}" && echo foo is empty || echo foo is set to $foo
foo is set to bar
Solution 4:[4]
I don't see a correct answer here yet that is POSIX compliant. First let me reiterate William Pursell's assertion that the code foo=
is indeed setting the variable foo
to an empty value the same as foo=""
. For foo
to be unset, it must either never be set or unset with unset foo
.
Matteo's answer is correct, but there are caveats. When you run set
in bash and posix
mode is disabled, it also prints all of the defined functions as well. This can be suppressed like this:
isvar() (
[ -n "$BASH" ] && set -o posix
set | grep -q "^$1="
)
By writing it as a sub-shell function, we don't need to worry about what the state of bash's posix
setting after we're done.
However, you can still get false-positives from variables whose values contain carriage returns, so understand that this is not 100% foolproof.
$ a="
> b=2
> "
$ set | grep ^b=
b=2
So for maximum correctness you can exploit bash's -v
test, when available.
isvar() {
if [ -n "$BASH" ]; then
[ -v "$1" ]
else
set | grep -q "^$1="
fi
}
Perhaps somebody has a library somewhere that supports other shells' extensions as well. Essentially, this is a weakness in the POSIX specification and it just hasn't been seen as warranting amendment.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Matteo |
Solution 2 | |
Solution 3 | William Pursell |
Solution 4 |