Bash Parameter Expansion's ${foo:?msg}
already does this, sort of...
${parameter:?word}
If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard error and the shell, if it is not interactive, exits. Otherwise, the value of parameter is substituted.
That exits
can throw some odd behavior, though, so be aware.
It's easy to test these with :
(which is just a synonym for true
, which returns a success, but accepts arguments it ignores, letting the interpreter parse them and very possibly fail... but a fail exits and will never even bother to execute :
, aborting the entire pipeline, so you really can't intuitively test its results.
Observe:
$: : ${some_val:?unset} ${other_val:?oopsie whoops...} && echo vars ok
bash: some_val: unset
$: some_val=1
$: : ${some_val:?unset} ${other_val:?oopsie whoops...} && echo vars ok
bash: other_val: oopsie whoops...
$: other_val=2
$: : ${some_val:?unset} ${other_val:?oopsie whoops...} && echo vars ok
vars ok
You'd think you could use ||
to catch the error, but it doesn't work, because a fail exits the pipeline entirely.
$: : ${some_val:?unset} ${other_val:?oopsie whoops...} || echo not executed
bash: other_val: oopsie whoops...
The echo is in fact not executed. To get it to fire, put the tests in a subshell -
$: ( : ${some_val:?unset} ${other_val:?oopsie whoops...} ) || echo executed
bash: other_val: oopsie whoops...
executed
The ||
is now testing the exit code from the subshell...
But at this point the construct is really no simpler than an if
structure, and is considerably harder to read and maintain, especially for those who come after you, so... Have you really gained anything? If you put it in a script, the script exits on the fail.
$: cat tst
#! /bin/bash
: ${some_val:?unset} && : ${other_val:?oopsie whoops...}
date
$: ./tst
./tst: line 2: some_val: unset
$: some_val=1 ./tst
./tst: line 2: other_val: oopsie whoops...
$: some_val=1 other_val=2 ./tst
Thu Jan 26 13:10:47 CST 2023
For simple scripts, this may be fine, but consider carefully.
It does make it reasonably easy to implement what you wanted as a function and customize behavior, though -
$: cat tst
#! /bin/bash
chk() { local -n v="$1"; local msg="${2:-unset}"; ( : ${v:?$1: $msg} ); }
chk some_val; echo chk returned $?;
chk other_val "oopsie whoops!!"; echo chk returned $?;
date
$: ./tst
./tst: line 2: v: some_val: unset
chk returned 1
./tst: line 2: v: other_val: oopsie whoops!!
chk returned 1
Thu Jan 26 13:28:29 CST 2023
$: some_val=1 ./tst
chk returned 0
./tst: line 2: v: other_val: oopsie whoops!!
chk returned 1
Thu Jan 26 13:29:55 CST 2023
$: other_val=1 ./tst
./tst: line 2: v: some_val: unset
chk returned 1
chk returned 0
Thu Jan 26 13:30:05 CST 2023
$: some_val=1 other_val=2 ./tst
chk returned 0
chk returned 0
Thu Jan 26 13:30:16 CST 2023