*Note: `zsh` will complain about "bad patterns" if you don't configure it to accept "inline comments" for most of the examples here and don't run them through a proxy shell as I have done with `sh <<-\CMD`.*
Ok, so, as I stated in the comments above, I don't know specifically about *bash's `set -E`*, but I know that POSIX compatible shells provide a simple means of testing a value if you desire it:
sh -evx <<-\CMD
_test() { echo $( ${empty:?error string} ) &&\
echo "echo still works"
}
_test && echo "_test doesnt fail"
# END
CMD
sh: line 1: empty: error string
+ echo
+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail
Above you'll see that though I used *`parameter expansion`* to test *`${empty?} _test()`* still *`return`s* a pass - as is evinced in the last *`echo`* This occurs because the failed value kills the *`$( command substitution )`* subshell that contains it, but its parent shell - *`_test`* at this time - keeps on trucking. And *`echo`* doesn't care - it's plenty happy to serve only a *`\newline; echo`* is ***not*** a test.
But consider this:
sh -evx <<-\CMD
_test() { echo $( ${empty:?error string} ) &&\
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
_test ||\
echo "this doesnt even print"
# END
CMD
_test+ sh: line 1: empty: function doesnt run
Because I fed *`_test()'s`* input with a pre-evaluated parameter in the *`INIT here-document`* now the *`_test()`* function doesn't even attempt to run at all. What's more the *`sh`* shell apparently gives up the ghost entirely and *`echo "this doesnt even print"` **doesn't even print.***
Probably that is ***not*** what you want.
This happens because the *`${var?}`* style parameter-expansion is ***designed to quit the `shell`*** in the event of a missing parameter, it [works like this][1]:
> ***`${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.
I won't copy/paste the entire document, but if you want a failure for a **`set but null`** value you use the form:
> *`${var` **:?** `error message }`*
With the *`:colon`* as above. If you want a *`null`* value to succeed, just omit the colon. You can also negate it and fail only for set values, as I'll show in a moment.
Another run of *`_test():`*
sh <<-\CMD
_test() { echo $( ${empty:?error string} ) &&\
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
echo "this runs" |\
( _test ; echo "this doesnt" ) ||\
echo "now it prints"
# END
CMD
this runs
sh: line 1: empty: function doesnt run
now it prints
This works with all kinds of quick tests, but above you'll see that *`_test()`*, run from the middle of the *`pipeline`* fails, and in fact its containing *`command list`* subshell fails entirely, as none of the commands within the function run nor the following *`echo`* run at all, though it is also shown that it can easily be tested because *`echo "now it prints"` **now prints.***
The devil is in the details, I guess. In the above case, the shell that exits is *not* the script's *`_main | logic | pipeline`* but the ***`( subshell in which we ${test?} ) ||`*** so a little sandboxing is called for.
And it may not be obvious, but if you wanted to only pass for the opposite case, or only *`set=`* values, it's fairly simple as well:
sh <<-\CMD
N= #N is NULL
_test=$N #_test is also NULL and
v="something you would rather do without"
( #this subshell dies
echo "v is ${v+set}: and its value is ${v:+not NULL}"
echo "So this ${_test:-"\$_test:= will equal ${_test:="$v"}"}"
${_test:+${N:?so you test for it with a little nesting}} : &&\
echo "sure wish we could do some other things"
)
( #this subshell does some other things
unset v #to ensure it is definitely unset
echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
echo "So this ${_test:-"\$_test:= will equal ${_test:="$v"}NULL"}"
: ${_test:+${N:?is never substituted}} &&\
echo "so now we can do some other things"
)
# END
CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 6: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
The above example takes advantage of all 4 forms of POSIX parameter substitution and their various *`:colon null`* or *`not null`* tests. There is more information in the link above, and [here it is again][1].
And I guess we should show our ***`_test`*** function work, too, right? We just declare ***`empty=something`*** as a parameter to our function (or any time beforehand):
sh <<-\CMD
_test() { echo $( echo ${empty:?error string} ) &&\
echo "echo still works" ; } 2<<-INIT
${empty?tested as a pass before function runs}
INIT
echo "this runs" >&2 |\
( empty=not_empty _test ; echo "yay! I print now!" ) ||\
echo "suspiciously quiet"
# END
CMD
this runs
not_empty
echo still works
yay! I print now!
It should be noted that this evaluation stands alone - it requires no additional test to fail. A couple more examples:
sh <<-\CMD
empty=
${empty?null, no colon, no failure}
unset empty
echo "${empty?this is stderr} this is not"
# END
CMD
sh: line 3: empty: this is stderr
sh <<-\CMD
_input_fn() { set -- "$@" #redundant
echo ${*?WHERES MY DATA?}
#echo is not necessary though
shift #sure hope we have more than $1 parameter
: ${*?WHERES MY DATA?} #: do nothing, gracefully
}
_input_fn heres some stuff
_input_fn one #here
# shell dies - third try doesnt run
_input_fn you there?
# END
CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?
[1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02