Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Differentiate Between an Empty or Unset String Variable with Bash

differentiate-between-an-empty-or-unset-string-variable-with-bash.jpg

We'll go over a POSIX compliant solution using parameter expansion as well as a Bash 4.2+ variable existence check.

Quick Jump:

In a lot of cases you can do var="${var:-}" which will either use var if it’s been set or fallback to an empty string. Then your script can do whatever you need with ${var} because you know it’s defined and either empty or has a value. Then in your script, if it’s empty maybe you have a custom validation error. I use this pattern most of the time.

But every once in a while you might want to be able to tell if a variable is unset (undefined, commented out in a config file, etc.) vs being set but with an empty value.

We all know you can do this to check if a variable is empty:

if [ -z "${var:-}" ]; then
  echo "var= | Empty, but we don't if it's unset or an empty string"
else
  echo "var=${var} | Set and non-empty"
fi

But the problem is, we don’t know why it’s empty. Was it unset or explicitly set as an empty string? This detail can matter depending on what you’re building. Maybe you only want to define a variable with some value if it’s unset and if a user already has that variable set and it’s empty, that’s ok and you want to keep it empty.

We can get this behavior to work, even when using set -o nounset.

# Parameter Expansion

This is POSIX compliant so it’ll work pretty much everywhere:

#!/usr/bin/env sh

set -o errexit
set -o nounset

# var=
# var="hello"

# This uses POSIX compliant parameter expansion.
#
# If the variable is not set then ${var+x} expands to "", otherwise it expands
# to "x" and "x" can be any string, it's idiomatic to use "x".
#
# Given the above, once that "is unset?" check is done, we can then figure out
# if it's empty or not using a regular -z check on ${var}.
if [ -z "${var+x}" ]; then
  # Notice the :- fallback to protect against nounset. Normally you may not
  # need to reference it since you know it's unset but here's how you can.
  echo "var=${var:-} | Unset"
# We know it's set, but is it empty or not?
elif [ -z "${var}" ]; then
  echo "var=${var} | Empty"
# It must be set and has a value.
else
  echo "var=${var} | Set and non-empty"
fi

Here’s a few outputs:

# When var is commented out.
$ ./posix-compliant
var= | Unset

# When var= is set.
$ ./posix-compliant
var= | Empty

# When var=hello is set.
$ ./posix-compliant
var=hello | Set and non-empty

You can also rewrite the above as a case statement if it helps understand it more since it separates out the conditions a little more clearly. This does the exact same thing as the above:

case "${var+x}" in
"")
  echo "var=${var:-} | Unset"
  ;;
x)
  if [ -z "${var}" ]; then
    echo "var=${var} | Empty"
  else
    echo "var=${var} | Set and non-empty"
  fi
  ;;
esac

# Bash 4.2+ Built-In Variable Existence

#!/usr/bin/env bash

set -o errexit
set -o pipefail
set -o nounset

#var=
#var="hello"

# Uses Bash's built-in variable existence check instead of parameter expansion.
if [[ ! -v var ]]; then
  echo "var=${var:-} | Unset"
elif [ -z "${var}" ]; then
  echo "var=${var} | Empty"
else
  echo "var=${var} | Set and non-empty"
fi

This produces the same output as the parameter expansion solution, it’s just a little more clear. If your script already depends on Bash 4.2+ features you can consider using this.

The video below shows all 3 examples.

# Demo Video

Timestamps

  • 0:10 – When you won’t need this
  • 0:55 – Using -z by itself doesn’t work
  • 1:31 – POSIX compliant parameter expansion
  • 3:04 – The same but with a case statement
  • 4:13 – Built in Bash 4.2+ variable existence check

When was the last time you used this pattern? Let me know below.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per year (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments