It's important to remember that an empty shell variable is not the same thing as an unset shell variable - though this distinction cannot be made using either form of [ test ]
. All that test
can do is report on the value of a variable's expansion - it is parameter expansion that determines that. For example:
var=value
echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
###OUTPUT###
I am $var and I am set. I am currently not empty.
Again:
var=
echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
###OUTPUT###
I am $var and I am set. I am currently empty.
With [ test ]
:
{ _nstr() { echo 'I just evaluated a ""null string!' ; }
[ -z "$var" ] && _nstr
[ -z "" ] && _nstr
unset var
[ -z "$var" ] && _nstr
echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
}
###OUTPUT###
I just evaluated a ""null string!
I just evaluated a ""null string!
I just evaluated a ""null string!
Hopefully this drives it home:
var=value
[ -z "${var+}" ] && _nstr
###OUTPUT###
I just evaluated a ""null string!
If you want to portably determine if a variable is empty this will do it:
${var:+false} [ -n "${var+1}" ] && echo \$var is set and null
I think you could make very good use of the shell's parameter expansion for cases like these and in some cases even kill two birds with one stone (see the first example). This should definitely be portable. What follows is an incomplete copy/paste from the open group's POSIX shell guidelines found here: http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
${parameter:-word}
- Use Default Values. If parameter is
unset
or null
, the expansion of word shall be substituted; otherwise, the value of parameter shall be substituted.
${parameter:=word}
- Assign Default Values. If parameter is
unset
or null
, the expansion of word shall be assigned to parameter. In all cases, the final value of parameter shall be substituted. Only variables, not positional parameters or special parameters, can be assigned in this way.
${parameter:?[word]}
- Indicate Error if Null or Unset. If parameter is
unset
or null
, the expansion of word (or a message indicating it is unset if word is omitted) shall be written to standard error and the shell exits with a non-zero exit status. Otherwise, the value of parameter shall be substituted. An interactive shell need not exit.
${parameter:+word}
- Use Alternative Value. If parameter is
unset
or null
, null
shall be substituted; otherwise, the expansion of word shall be substituted.
EXAMPLES:
${parameter:-word}
- In this example,
ls
is executed only if x
is null
or unset
. (The $( ls)
command substitution notation is explained in Command Substitution.)
${x:-$(ls)}
${parameter:=word}
% unset X
% echo ${X:=abc}
abc
EDIT:
I just noticed Ashish's one-liner, and because it felt kinda cheap just pasting that in, I'd like to do something. So here's a POSIX parameter expansion set-test one-liner:
_JAIL="" ; echo "_JAIL is ${_JAIL:-unset or null.}"
Well, still cheap, I guess.
EDIT2:
So I just noticed this: "...while these work for the scripts I write now, I'd like to know a way, that will work in any reasonably compatible sh-like shell, even on some more obscure environment than a Linux PC with GNU userland and bash or dash..."
I am sorry I'm not personally an expert, but I first picked up the following method of defining important shell variables from arguments that may or may not have been passed after digging through some fairly expertly written initramfs busybox scripts. It will work as expected pretty much everywhere.
It's kinda beautiful because you only set the variables to the default condition if the user doesn't define them without any extra work to check if they did, really. There are tons of other uses of course, I'm just not smart enough to use them like I should.
_VAR1=${1:-/default/path} ; _VAR2=${2:-"$_default_size"}
EDIT3:
phs was right to specify that his method tests for empty variables not absent ones as was posed in the question. This method can do the same depending on whether the center colon is used. I've copied from the POSIX guidelines below a sort of a crap table to demonstrate this.
{subs='substitute'; params='parameters'; assn='assign'; err='error'}
"$parameter" == |set && !null |set && null| !set
------------------ ------------- ----------- ----------
${parameter:-word} | subs params | subs word | subs word
------------------ ------------- ----------- ----------
${parameter-word} | subs params | subs null | subs word
------------------ ------------- ----------- ----------
${parameter:=word} | subs params | assn word | assn word
------------------ ------------- ----------- ----------
${parameter=word} | subs params | subs null | assn word
------------------ ------------- ----------- ----------
${parameter:?word} | subs params | err; exit | err; exit
------------------ ------------- ----------- ----------
${parameter?word} | subs params | subs null | err; exit
------------------ ------------- ----------- ----------
${parameter:+word} | subs word | subs null | subs null
------------------ ------------- ----------- ----------
${parameter+word} | subs word | subs word | subs null