'Test for a Bash variable being unset, using a function

A simple Bash variable test goes:

${varName:?    "${varName} is not defined"}

I'd like to reuse this, by putting it in a function. How can I do it?

The following fails

#
# Test a variable exists
tvar(){
 val=${1:?    "${1}    must be defined, preferably in $basedir"}
 if [ -z ${val}  ]
     then
     echo Zero length value
 else
     echo ${1} exists, value ${1}
 fi
}

I.e., I need to exit if the test fails.



Solution 1:[1]

Thanks to lhunath's answer, I was led to a part of the Bash man page that I've overlooked hundreds of times:

When not performing substring expansion, bash tests for a parameter that is unset or null; omitting the colon results in a test only for a parameter that is unset.

This prompted me to create the following truth table:

Unset Set, but null Set and not null Meaning
${var-_} T F T Not null or not set
${var:-_} T T T Always true, use for subst.
$var F F T 'var' is set and not null
${!var[@]} F T T 'var' is set

This table introduces the specification in the last row. The Bash man page says "If name is not an array, expands to 0 if name is set and null otherwise." For purposes of this truth table, it behaves the same even if it's an array.

Solution 2:[2]

You're looking for indirection.

assertNotEmpty() {
    : "${!1:? "$1 is empty, aborting."}"
}

That causes the script to abort with an error message if you do something like this:

$ foo=""
$ assertNotEmpty foo
bash: !1:  foo is empty, aborting.

If you just want to test whether foo is empty, instead of aborting the script, use this instead of a function:

[[ $foo ]]

For example:

until read -p "What is your name? " name && [[ $name ]]; do
    echo "You didn't enter your name.  Please, try again." >&2
done

Also, note that there is a very important difference between an empty and an unset parameter. You should take care not to confuse these terms! An empty parameter is one that is set, but just set to an empty string. An unset parameter is one that doesn't exist at all.

The previous examples all test for empty parameters. If you want to test for unset parameters and consider all set parameters OK, whether they're empty or not, use this:

[[ ! $foo && ${foo-_} ]]

Use it in a function like this:

assertIsSet() {
    [[ ! ${!1} && ${!1-_} ]] && {
        echo "$1 is not set, aborting." >&2
        exit 1
    }
}

Which only aborts the script when the parameter name you pass denotes a parameter that isn't set:

$ ( foo="blah"; assertIsSet foo; echo "Still running." )
Still running.

$ ( foo=""; assertIsSet foo; echo "Still running." )
Still running.

$ ( unset foo; assertIsSet foo; echo "Still running." )
foo is not set, aborting.

Solution 3:[3]

You want to use [ -z ${parameter+word} ]

Some part of man bash:

Parameter Expansion
    ...
    In each of the cases below, word is subject to
    tilde expansion, parameter expansion, command
    substitution, and arithmetic expansion.  When
    not performing substring expansion, bash
    tests for a parameter that is unset or
    null; omitting the colon results in a
    test only for a parameter that is unset.
    ...
    ${parameter:+word}
           Use Alternate Value.  If parameter is
           null or unset, nothing is substituted,
           otherwise the expansion of word is
           substituted.
    ...

In other words:

${parameter+word}
       Use Alternate Value.  If parameter is unset,
       nothing is substituted, otherwise the
       expansion of word is substituted.

Some examples:

set | grep FOOBAR
if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi

Output:

it is unset

declare FOOBAR
if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
FOOBAR=
if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
FOOBAR=1
if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
unset FOOBAR
if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi

Output:

it is unset

Solution 4:[4]

This function tests for variables that are currently set. The variable may even be an array. Note that in Bash: 0 == TRUE, 1 == FALSE.

function var.defined {
    eval '[[ ${!'$1'[@]} ]]'
}

# Typical usage of var.defined {}

declare you="Your Name Here" ref='you';

read -p "What's your name: " you;

if var.defined you; then   # Simple demo using literal text

    echo "BASH recognizes $you";
    echo "BASH also knows a reference to $ref as ${!ref}, by indirection.";

fi

unset you # Have just been killed by a master :D

if ! var.defined $ref; then    # Standard demo using an expanded literal value

    echo "BASH doesn't know $ref any longer";

fi

read -s -N 1 -p "Press any key to continue...";
echo "";

So to be clear here, the function tests literal text. Every time a command is called in Bash, variables are generally 'swapped-out' or 'substituted' with the underlying value unless:

  • $varRef ($) is escaped: $varRef
  • $varRef is single quoted '$varRef'

Solution 5:[5]

I.e., I need to exit if the test fails.

The code:

${varName:?    "${varName} is not defined"}

will return a nonzero exit code when there is not a variable named "varName". The exit code of the last command is saved in $?.

About your code:

val=${1:?    "${1}    must be defined, preferably in $basedir"}

Maybe it is not doing what you need. In the case that $1 is not defined, the "${1}" will be substituted with nothing. Probably you want use the single quotes that literally writes ${1} without substitution.

val=${1:?    '${1}    must be defined, preferably in $basedir'

Solution 6:[6]

I am unsure if this is exactly what you want, but a handy trick I use when writing a new and complex script is to use "set -o":

set -o # Will make the script bomb out when it finds an unset variable

For example,

$ grep '$1' chex.sh
case "$1" in

$ ./chex.sh
./chex.sh: line 111: $1: unbound variable

$ ./chex.sh foo
incorrect/no options passed.. exiting

Solution 7:[7]

if set | grep -q '^VARIABLE='
then
    echo VARIABLE is set
fi

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 Peter Mortensen
Solution 2 Peter Mortensen
Solution 3 Peter Mortensen
Solution 4 Peter Mortensen
Solution 5 Peter Mortensen
Solution 6 Peter Mortensen
Solution 7 Peter Mortensen