How to check if a variable is set in Bash

Viewed 1956415

How do I know if a variable is set in Bash?

For example, how do I check if the user gave the first parameter to a function?

function a {
    # if $1 is set ?
}
37 Answers

Summary

  • Use test -n "${var-}" to check if the variable is not empty (and hence must be defined/set too). Usage:

    if test -n "${var-}"; then
      echo "var is set to <$var>"
    else
      echo "var is not set or empty"
    fi
    
  • Use test -n "${var+x}" to check if the variable is defined/set (even if it's empty). Usage:

    if test -n "${var+x}"; then
      echo "var is set to <$var>"
    else
      echo "var is not set"
    fi
    

Note that the first use case is much more common in shell scripts and this is what you will usually want to use.

Notes

  • This solution should work in all POSIX shells (sh, bash, zsh, ksh, dash)
  • Some of the other answers for this question are correct but may be confusing for people unexperienced in shell scripting, so I wanted to provide a TLDR answer that will be least confusing for such people.

Explanation

To understand how this solution works, you need to understand the POSIX test command and POSIX shell parameter expansion (spec), so let's cover the absolute basics needed to understand the answer.

The test command evaluates an expression and returns true or false (via its exit status). The operator -n returns true if the operand is a non-empty string. So for example, test -n "a" returns true, while test -n "" returns false. Now, to check if a variable is not empty (which means it must be defined), you could use test -n "$var". However, some shell scripts have an option set (set -u) that causes any reference to undefined variables to emit an error, so if the variable var is not defined, the expression $var will cause an error. To handle this case correctly, you must use variable expansion, which will tell the shell to replace the variable with an alternative string if it's not defined, avoiding the aforementioned error.

The variable expansion ${var-} means: if the variable var is undefined (also called "unset"), replace it with an empty string. So test -n "${var-}" will return true if $var is not empty, which is almost always what you want to check in shell scripts. The reverse check, if $var is undefined or not empty, would be test -z "${var-}".

Now to the second use case: checking if the variable var is defined, whether empty or not. This is a less common use case and slightly more complex, and I would advise you to read Lionels's great answer to better understand it.

I'm surprised nobody has tried to write a shell script to programmatically generate the infamously hard to grok table. Since we're here trying to learn coding techniques, why not express the answer in code? :) Here's my take (should work in any POSIX shell):

H="+-%s-+-%s----+-%s----+-%s--+\n"       # table divider printf format
R="| %-10s | %-10s | %-10s | %-10s |\n"  # table row printf format

S='V'     # S is a variable that is set-and-not-null
N=''      # N is a variable that is set-but-null (empty "")
unset U   # U is a variable that is unset

printf "$H" "----------" "-------" "-------" "---------";
printf "$R" "expression" "FOO='V'" "FOO='' " "unset FOO";
printf "$H" "----------" "-------" "-------" "---------";
printf "$R" "\${FOO:-x}" "${S:-x}" "${N:-x}" "${U:-x}  "; S='V';N='';unset U
printf "$R" "\${FOO-x} " "${S-x} " "${N-x} " "${U-x}   "; S='V';N='';unset U
printf "$R" "\${FOO:=x}" "${S:=x}" "${N:=x}" "${U:=x}  "; S='V';N='';unset U
printf "$R" "\${FOO=x} " "${S=x} " "${N=x} " "${U=x}   "; S='V';N='';unset U
#                                  "${N:?x}" "${U:?x}  "
printf "$R" "\${FOO:?x}" "${S:?x}" "<error>" "<error>  "; S='V';N='';unset U
#                                            "${U?x}   "
printf "$R" "\${FOO?x} " "${S?x} " "${N?x} " "<error>  "; S='V';N='';unset U
printf "$R" "\${FOO:+x}" "${S:+x}" "${N:+x}" "${U:+x}  "; S='V';N='';unset U
printf "$R" "\${FOO+x} " "${S+x} " "${N+x} " "${U+x}   "; S='V';N='';unset U
printf "$H" "----------" "-------" "-------" "---------";

And the output of running the script:

+------------+------------+------------+------------+
| expression | FOO='V'    | FOO=''     | unset FOO  |
+------------+------------+------------+------------+
| ${FOO:-x}  | V          | x          | x          |
| ${FOO-x}   | V          |            | x          |
| ${FOO:=x}  | V          | x          | x          |
| ${FOO=x}   | V          |            | x          |
| ${FOO:?x}  | V          | <error>    | <error>    |
| ${FOO?x}   | V          |            | <error>    |
| ${FOO:+x}  | x          |            |            |
| ${FOO+x}   | x          | x          |            |
+------------+------------+------------+------------+

The script is missing a few features like displaying when the side-effect assignments do (or do not) take place, but maybe some other more ambitious person wants to take this starting point and run with it.

To clearly answer the OP's question of how to determine whether a variable is set, Lionel's answer is correct:

if test "${name+x}"; then
    echo 'name is set'
else
    echo 'name is not set'
fi

This question already has a lot of answers, but none of them offered bona fide Boolean expressions to clearly differentiate between variables values.

Here are some unambiguous expressions that I worked out:

+-----------------------+-------------+---------+------------+
| Expression in script  | name='fish' | name='' | unset name |
+-----------------------+-------------+---------+------------+
| test "$name"          | TRUE        | f       | f          |
| test -n "$name"       | TRUE        | f       | f          |
| test ! -z "$name"     | TRUE        | f       | f          |
| test ! "${name-x}"    | f           | TRUE    | f          |
| test ! "${name+x}"    | f           | f       | TRUE       |
+-----------------------+-------------+---------+------------+

By the way, these expressions are equivalent: test <expression> <=> [ <expression> ]

Other ambiguous expressions to be used with caution:

+----------------------+-------------+---------+------------+
| Expression in script | name='fish' | name='' | unset name |
+----------------------+-------------+---------+------------+
| test "${name+x}"     | TRUE        | TRUE    | f          |
| test "${name-x}"     | TRUE        | f       | TRUE       |
| test -z "$name"      | f           | TRUE    | TRUE       |
| test ! "$name"       | f           | TRUE    | TRUE       |
| test ! -n "$name"    | f           | TRUE    | TRUE       |
| test "$name" = ''    | f           | TRUE    | TRUE       |
+----------------------+-------------+---------+------------+

To test if a variable var is set: [ ${var+x} ].

To test if a variable is set by name: [ ${!name+x} ].

To test if a positional parameter is set: [ ${N+x} ], where N is actually an integer.

This answer is almost similar to Lionel’s but explore a more minimalist take by omitting the -z.

To test if a named variable is set:

function is_set {
    local v=$1
    echo -n "${v}"
    if [ ${!v+x} ]; then
        echo " = '${!v}'"
    else
        echo " is unset"
    fi
}

To test if a positional parameter is set:

function a {
    if [ ${1+x} ]; then
        local arg=$1
        echo "a '${arg}'"
    else
        echo "a: arg is unset"
    fi
}

Testing shows that extra care with white spaces and valid test expressions is not needed.

set -eu

V1=a
V2=
V4=-gt
V5="1 -gt 2"
V6="! -z 1"
V7='$(exit 1)'

is_set V1
is_set V2
is_set V3
is_set V4
is_set V5
is_set V6
is_set V7

a 1
a
a "1 -gt 2"
a 1 -gt 2
$ ./test.sh

V1 = 'a'
V2 = ''
V3 is unset
V4 = '-gt'
V5 = '1 -gt 2'
V6 = '! -z 1'
V7 = '$(exit 1)'
a '1'
a: arg is unset
a '1 -gt 2'
a '1'

Finally, notice the set -eu which protects us from common errors, such as typos in variable names. I recommend its usage, but this implies that the difference between an unset variable and a variable set with an empty string is handled correctly.

The answers above do not work when Bash option set -u is enabled. Also, they are not dynamic, e.g., how to test is variable with name "dummy" is defined? Try this:

is_var_defined()
{
    if [ $# -ne 1 ]
    then
        echo "Expected exactly one argument: variable name as string, e.g., 'my_var'"
        exit 1
    fi
    # Tricky.  Since Bash option 'set -u' may be enabled, we cannot directly test if a variable
    # is defined with this construct: [ ! -z "$var" ].  Instead, we must use default value
    # substitution with this construct: [ ! -z "${var:-}" ].  Normally, a default value follows the
    # operator ':-', but here we leave it blank for empty (null) string.  Finally, we need to
    # substitute the text from $1 as 'var'.  This is not allowed directly in Bash with this
    # construct: [ ! -z "${$1:-}" ].  We need to use indirection with eval operator.
    # Example: $1="var"
    # Expansion for eval operator: "[ ! -z \${$1:-} ]" -> "[ ! -z \${var:-} ]"
    # Code  execute: [ ! -z ${var:-} ]
    eval "[ ! -z \${$1:-} ]"
    return $?  # Pedantic.
}

Related: In Bash, how do I test if a variable is defined in "-u" mode

I found a (much) better code to do this if you want to check for anything in $@.

if [[ $1 = "" ]]
then
  echo '$1 is blank'
else
  echo '$1 is filled up'
fi

Why this all? Everything in $@ exists in Bash, but by default it's blank, so test -z and test -n couldn't help you.

Update: You can also count number of characters in a parameters.

if [ ${#1} = 0 ]
then
  echo '$1 is blank'
else
  echo '$1 is filled up'
fi

After skimming all the answers, this also works:

if [[ -z $SOME_VAR ]]; then read -p "Enter a value for SOME_VAR: " SOME_VAR; fi
echo "SOME_VAR=$SOME_VAR"

if you don't put SOME_VAR instead of what I have $SOME_VAR, it will set it to an empty value; $ is necessary for this to work.

Declare a simple function is_set which uses declare -p to test directly if the variable exists.

$ is_set() {
    declare -p $1 >/dev/null 2>&1
}

$ is_set foo; echo $?
0

$ declare foo

$ is_set foo; echo $?
1
Related