How to check if a variable is set in Bash

Asked 2023-09-20 20:32:55 View 170,551

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 ?
}
  • if test $# -gt 0; then printf 'arg <%s>\n' "$@"; fi. - anyone
  • Note to solution-seekers: There are many highly-rated answers to this question that answer the question "is variable non-empty". The more correction solutions ("is variable set") are mentioned in answers by Jens and Lionel below. - anyone
  • Also Russell Harmon and Seamus are correct with their -v test, although this is seemingly only available on new versions of bash and not portable across shells. - anyone
  • As pointed out by @NathanKidd, correct solutions are given by Lionel and Jens. prosseek, you should switch your accepted answer to one of these. - anyone
  • ... or the incorrect answer could be downvoted by the more discerning among us, since @prosseek is not addressing the problem. - anyone

Answers

(Usually) The right way

if [ -z ${var+x} ]; then echo "var is unset"; else echo "var is set to '$var'"; fi

where ${var+x} is a parameter expansion which evaluates to nothing if var is unset, and substitutes the string x otherwise.

Quotes Digression

Quotes can be omitted (so we can say ${var+x} instead of "${var+x}") because this syntax & usage guarantees this will only expand to something that does not require quotes (since it either expands to x (which contains no word breaks so it needs no quotes), or to nothing (which results in [ -z ], which conveniently evaluates to the same value (true) that [ -z "" ] does as well)).

However, while quotes can be safely omitted, and it was not immediately obvious to all (it wasn't even apparent to the first author of this quotes explanation who is also a major Bash coder), it would sometimes be better to write the solution with quotes as [ -z "${var+x}" ], at the very small possible cost of an O(1) speed penalty. The first author also added this as a comment next to the code using this solution giving the URL to this answer, which now also includes the explanation for why the quotes can be safely omitted.

(Often) The wrong way

if [ -z "$var" ]; then echo "var is blank"; else echo "var is set to '$var'"; fi

This is often wrong because it doesn't distinguish between a variable that is unset and a variable that is set to the empty string. That is to say, if var='', then the above solution will output "var is blank".

The distinction between unset and "set to the empty string" is essential in situations where the user has to specify an extension, or additional list of properties, and that not specifying them defaults to a non-empty value, whereas specifying the empty string should make the script use an empty extension or list of additional properties.

The distinction may not be essential in every scenario though. In those cases [ -z "$var" ] will be just fine.

Answered   2023-09-20 20:32:55

  • @Garrett, your edit has made this answer incorrect, ${var+x} is the correct substitution to use. Using [ -z ${var:+x} ] produces no different result than [ -z "$var" ]. - anyone
  • This doesn't work. I'm getting "not set" regardless of whether var is set to a value or not (cleared with "unset var", "echo $var" produces no output). - anyone
  • For the solution's syntax ${parameter+word}, the official manual section is gnu.org/software/bash/manual/… ; however, a bug in that, it doesn't very clearly mention this syntax but says just quote(Omitting the colon[ ":"] results in a test only for a parameter that is unset. .. ${parameter:+word} [means] If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.); the cited ref pubs.opengroup.org/onlinepubs/9699919799/utilities/… (good to know)has much clearer docs here. - anyone
  • It looks like quotes are required when you use multiple expressions, e.g. [ -z "" -a -z ${var+x} ] gets bash: [: argument expected in bash 4.3-ubuntu (but not e.g. zsh). I really dislike the answer using a syntax that happens to work in some cases. - anyone
  • Using a simple [ -z $var ] is no more "wrong" than omitting quotes. Either way, you're making assumptions about your input. If you are fine treating an empty string as unset, [ -z $var ] is all you need. - anyone

To check for non-null/non-zero string variable, i.e. if set, use

if [ -n "$1" ]

It's the opposite of -z. I find myself using -n more than -z.

You would use it like:

if [ -n "$1" ]; then
  echo "You supplied the first parameter!"
else
  echo "First parameter not supplied."
fi

Answered   2023-09-20 20:32:55

  • I usually prefer [[ ]] over [ ], as [[ ]] is more powerfull and causes less problems in certain situations (see this question for an explanation of the difference between the two). The question specifically asks for a bash solution and doesn't mention any portability requirements. - anyone
  • The question is how one can see if a variable is set. Then you can't assume that the variable is set. - anyone
  • I agree, [] works better especially for shell (non-bash) scripts. - anyone
  • Note that -n is the default test, so more simply [ "$1" ] or [[ $1 ]] work as well. Also note that with [[ ]] quoting is unnecessary. - anyone
  • Seems it's not really opposite... Real opposite is if [ ! -z "$1" ] - anyone

Here's how to test whether a parameter is unset, or empty ("Null") or set with a value:

+--------------------+----------------------+-----------------+-----------------+
|   Expression       |       parameter      |     parameter   |    parameter    |
|   in script:       |   Set and Not Null   |   Set But Null  |      Unset      |
+--------------------+----------------------+-----------------+-----------------+
| ${parameter:-word} | substitute parameter | substitute word | substitute word |
| ${parameter-word}  | substitute parameter | substitute null | substitute word |
| ${parameter:=word} | substitute parameter | assign word     | assign word     |
| ${parameter=word}  | substitute parameter | substitute null | assign word     |
| ${parameter:?word} | substitute parameter | error, exit     | error, exit     |
| ${parameter?word}  | substitute parameter | substitute null | error, exit     |
| ${parameter:+word} | substitute word      | substitute null | substitute null |
| ${parameter+word}  | substitute word      | substitute word | substitute null |
+--------------------+----------------------+-----------------+-----------------+

Source: POSIX: Parameter Expansion:

In all cases shown with "substitute", the expression is replaced with the value shown. In all cases shown with "assign", parameter is assigned that value, which also replaces the expression.

To show this in action:

+--------------------+----------------------+-----------------+-----------------+
|   Expression       |  When FOO="world"    |  When FOO=""    |    unset FOO    |
|   in script:       |  (Set and Not Null)  |  (Set But Null) |     (Unset)     |
+--------------------+----------------------+-----------------+-----------------+
| ${FOO:-hello}      | world                | hello           | hello           |
| ${FOO-hello}       | world                | ""              | hello           |
| ${FOO:=hello}      | world                | FOO=hello       | FOO=hello       |
| ${FOO=hello}       | world                | ""              | FOO=hello       |
| ${FOO:?hello}      | world                | error, exit     | error, exit     |
| ${FOO?hello}       | world                | ""              | error, exit     |
| ${FOO:+hello}      | hello                | ""              | ""              |
| ${FOO+hello}       | hello                | hello           | ""              |
+--------------------+----------------------+-----------------+-----------------+

Answered   2023-09-20 20:32:55

  • @HelloGoodbye Yes it does work: set foo; echo ${1:-set but null or unset} echos "foo"; set --; echo ${1:-set but null or unset} echoes set but null ... - anyone
  • @HelloGoodbye The positional parameters can be set with, uh, set :-) - anyone
  • This answer is very confusing. Any practical examples on how to use this table? - anyone
  • @BenDavis The details are explained in the link to the POSIX standard, which references the chapter the table is taken from. One construct I use in almost any script I write is : ${FOOBAR:=/etc/foobar.conf} to set a default value for a variable if it is unset or null. - anyone
  • parameter is any variable name. word is some string to substitute depending on which syntax in the table you are using, and whether the variable $parameter is set, set but null, or unset. Not to make things more complicated, but word can also include variables! This can be very useful for passing optional args separated by a character. For example: ${parameter:+,${parameter}} outputs a comma separated ,$parameter if it is set but not null. In this case, word is ,${parameter} - anyone

While most of the techniques stated here are correct, Bash 4.2 supports an actual test for the presence of a variable (man bash), rather than testing the value of the variable.

[[ -v foo ]]; echo $?
# 1

foo=bar
[[ -v foo ]]; echo $?
# 0

foo=""
[[ -v foo ]]; echo $?
# 0

Notably, this approach will not cause an error when used to check for an unset variable in set -u / set -o nounset mode, unlike many other approaches, such as using [ -z.

Answered   2023-09-20 20:32:55

  • In bash 4.1.2, regardless of whether variable is set, [[ -v aaa ]]; echo $? ==> -bash: conditional binary operator expected -bash: syntax error near 'aaa' - anyone
  • The '-v' argument to the 'test' builtin was added in bash 4.2. - anyone
  • the -v argument was added as a complement to the -u shell option (nounset). With 'nounset' turned on (set -u), the shell will return an error and terminate, if it is not interactive. $# was good for checking the positional parameters. However, named variables needed some other way, other than clobbering, to identifying them as unset. It is unfortunate that this feature has come so late because many are bound to being earlier-version compatible, in which case they can't use it. The only other option is to use tricks or 'hacks' to get around it as shown above but it is most inelegant. - anyone
  • This is why I keep reading after the accepted/highest-voted answer. - anyone
  • This is the correct method for scalars and is also available in ksh. Watch out: it is [[ -v foo ]] and NOT [[ -v $foo ]] if you don't want to waste half an hour staring at your screen wondering what's going on. In addition to verifying it is set, you are usually also looking for a particular value, say 123. In that case I prefer to use [[ ${foo-123} -eq 123 ]] to condense [[ -v foo && $foo -eq 123 ]] - anyone

There are many ways to do this with the following being one of them:

if [ -z "$1" ]

This succeeds if $1 is null or unset.

Answered   2023-09-20 20:32:55

  • There is a difference between an unset parameter and a parameter with a null value. - anyone
  • I just want to be pedantic and point out this is the opposite answer to what the question poses. The questions asks if the variable IS set, not if the variable is not set. - anyone
  • [[ ]] is nothing but a portability pitfall. if [ -n "$1" ] should be used here. - anyone
  • This answer is incorrect. The question asks for a way to tell whether a variable is set, not whether it's set to a non-empty value. Given foo="", $foo has a value, but -z will merely report that it's empty. And -z $foo will blow up if you have set -o nounset. - anyone
  • It depends on the definition of "variable is set". If you want to check if the variable is set to a non-zero string, then mbranning answer is correct. But if you want to check if the variable is declared but not initialized (i.e foo=), then Russel's answer is correct. - anyone

I always find the POSIX table in the other answer slow to grok, so here's my take on it:

parameter expansion VARIABLE set VARIABLE empty VARIABLE unset
${VARIABLE-default} $VARIABLE "" "default"
${VARIABLE=default} $VARIABLE "" $(VARIABLE="default")
${VARIABLE?default} $VARIABLE "" exit 127
${VARIABLE+default} "default" "default" ""
${VARIABLE:-default} $VARIABLE "default" "default"
${VARIABLE:=default} $VARIABLE $(VARIABLE="default") $(VARIABLE="default")
${VARIABLE:?default} $VARIABLE exit 127 exit 127
${VARIABLE:+default} "default" "" ""

Note that each group (with and without preceding colon) has the same set and unset cases, so the only thing that differs is how the empty cases are handled.

With the preceding colon, the empty and unset cases are identical, so I would use those where possible (i.e. use :=, not just =, because the empty case is inconsistent).

Headings:

  • set means VARIABLE is non-empty (VARIABLE="something")
  • empty means VARIABLE is empty/null (VARIABLE="")
  • unset means VARIABLE does not exist (unset VARIABLE)

Values:

  • $VARIABLE means the result is the original value of the variable.
  • "default" means the result was the replacement string provided.
  • "" means the result is null (an empty string).
  • exit 127 means the script stops executing with exit code 127.
  • $(VARIABLE="default") means the result is "default" and that VARIABLE (previously empty or unset) will also be set equal to "default".

Answered   2023-09-20 20:32:55

  • You fixed just the ones that are actual coding errors (instances of unwanted indirection when working with variables). I made an edit to fix a few more cases where the dollar makes a difference in interpreting the description. BTW, all shell variables (with the exception of arrays) are string variables; it may just happen that they contain a string that can be interpreted as a number. Talking about strings only fogs the message. Quotes have nothing to do with strings, they are just an alternative to escaping. VARIABLE="" could be written as VARIABLE=. Still, the former is more readable. - anyone
  • Thanks for the table it is very useful, however I am trying to have the script exit if unset or empty. But the exit code I get is 1 not 127. - anyone
  • When VARIABLE is unset, I'm seeing that A=${VARIABLE=default} has A equal to "default", rather than the original value of VARIABLE (which was unset). Is your description of $(VARIABLE="default") correct? Perhaps I am misreading what you mean by original value. - anyone
  • Per the documentation, it says "In all cases, the final value of parameter shall be substituted." So perhaps the description should be changed to: $(VARIABLE="default") means the result is "default" and that VARIABLE will be set equal to "default" too. - anyone
  • A candidate for using the new table feature. - anyone

To see if a variable is nonempty, I use

if [[ $var ]]; then ...       # `$var' expands to a nonempty string

The opposite tests if a variable is either unset or empty:

if [[ ! $var ]]; then ...     # `$var' expands to the empty string (set or not)

To see if a variable is set (empty or nonempty), I use

if [[ ${var+x} ]]; then ...   # `var' exists (empty or nonempty)
if [[ ${1+x} ]]; then ...     # Parameter 1 exists (empty or nonempty)

The opposite tests if a variable is unset:

if [[ ! ${var+x} ]]; then ... # `var' is not set at all
if [[ ! ${1+x} ]]; then ...   # We were called with no arguments

Answered   2023-09-20 20:32:55

  • I too have been using this for a while; just in a reduced way: [ $isMac ] && param="--enable-shared" - anyone
  • @Bhaskar I think it's because this answer already provided the essentially the same answer (use ${variable+x} to get x iff $variable is set) almost half a year earlier. Also it has much more detail explaining why it's right. - anyone

Note

I'm giving a heavily Bash-focused answer because of the bash tag.

Short answer

As long as you're only dealing with named variables in Bash, this function should always tell you if the variable has been set, even if it's an empty array.

variable-is-set() {
    declare -p "$1" &>/dev/null
}

Why this works

In Bash (at least as far back as 3.0), if var is a declared/set variable, then declare -p var outputs a declare command that would set variable var to whatever its current type and value are, and returns status code 0 (success). If var is undeclared, then declare -p var outputs an error message to stderr and returns status code 1. Using &>/dev/null, redirects both regular stdout and stderr output to /dev/null, never to be seen, and without changing the status code. Thus the function only returns the status code.

Why other methods (sometimes) fail in Bash

  • [ -n "$var" ]: This only checks if ${var[0]} is nonempty. (In Bash, $var is the same as ${var[0]}.)
  • [ -n "${var+x}" ]: This only checks if ${var[0]} is set.
  • [ "${#var[@]}" != 0 ]: This only checks if at least one index of $var is set.

When this method fails in Bash

This only works for named variables (including $_), not certain special variables ($!, $@, $#, $$, $*, $?, $-, $0, $1, $2, ..., and any I may have forgotten). Since none of these are arrays, the POSIX-style [ -n "${var+x}" ] works for all of these special variables. But beware of wrapping it in a function since many special variables change values/existence when functions are called.

Shell compatibility note

If your script has arrays and you're trying to make it compatible with as many shells as possible, then consider using typeset -p instead of declare -p. I've read that ksh only supports the former, but haven't been able to test this. I do know that Bash 3.0+ and Zsh 5.5.1 each support both typeset -p and declare -p, differing only in which one is an alternative for the other. But I haven't tested differences beyond those two keywords, and I haven't tested other shells.

If you need your script to be POSIX sh compatible, then you can't use arrays. Without arrays, [ -n "${var+x}" ] works.

Comparison code for different methods in Bash

This function unsets variable var, evals the passed code, runs tests to determine if var is set by the evald code, and finally shows the resulting status codes for the different tests.

I'm skipping test -v var, [ -v var ], and [[ -v var ]] because they yield identical results to the POSIX standard [ -n "${var+x}" ], while requiring Bash 4.2+. I'm also skipping typeset -p because it's the same as declare -p in the shells I've tested (Bash 3.0 thru 5.0, and Zsh 5.5.1).

is-var-set-after() {
    # Set var by passed expression.
    unset var
    eval "$1"

    # Run the tests, in increasing order of accuracy.
    [ -n "$var" ] # (index 0 of) var is nonempty
    nonempty=$?
    [ -n "${var+x}" ] # (index 0 of) var is set, maybe empty
    plus=$?
    [ "${#var[@]}" != 0 ] # var has at least one index set, maybe empty
    count=$?
    declare -p var &>/dev/null # var has been declared (any type)
    declared=$?

    # Show test results.
    printf '%30s: %2s %2s %2s %2s\n' "$1" $nonempty $plus $count $declared
}

Test case code

Note that test results may be unexpected due to Bash treating non-numeric array indices as "0" if the variable hasn't been declared as an associative array. Also, associative arrays are only valid in Bash 4.0+.

# Header.
printf '%30s: %2s %2s %2s %2s\n' "test" '-n' '+x' '#@' '-p'
# First 5 tests: Equivalent to setting 'var=foo' because index 0 of an
# indexed array is also the nonindexed value, and non-numerical
# indices in an array not declared as associative are the same as
# index 0.
is-var-set-after "var=foo"                        #  0  0  0  0
is-var-set-after "var=(foo)"                      #  0  0  0  0
is-var-set-after "var=([0]=foo)"                  #  0  0  0  0
is-var-set-after "var=([x]=foo)"                  #  0  0  0  0
is-var-set-after "var=([y]=bar [x]=foo)"          #  0  0  0  0
# '[ -n "$var" ]' fails when var is empty.
is-var-set-after "var=''"                         #  1  0  0  0
is-var-set-after "var=([0]='')"                   #  1  0  0  0
# Indices other than 0 are not detected by '[ -n "$var" ]' or by
# '[ -n "${var+x}" ]'.
is-var-set-after "var=([1]='')"                   #  1  1  0  0
is-var-set-after "var=([1]=foo)"                  #  1  1  0  0
is-var-set-after "declare -A var; var=([x]=foo)"  #  1  1  0  0
# Empty arrays are only detected by 'declare -p'.
is-var-set-after "var=()"                         #  1  1  1  0
is-var-set-after "declare -a var"                 #  1  1  1  0
is-var-set-after "declare -A var"                 #  1  1  1  0
# If 'var' is unset, then it even fails the 'declare -p var' test.
is-var-set-after "unset var"                      #  1  1  1  1

Test output

The test mnemonics in the header row correspond to [ -n "$var" ], [ -n "${var+x}" ], [ "${#var[@]}" != 0 ], and declare -p var, respectively.

                         test: -n +x #@ -p
                      var=foo:  0  0  0  0
                    var=(foo):  0  0  0  0
                var=([0]=foo):  0  0  0  0
                var=([x]=foo):  0  0  0  0
        var=([y]=bar [x]=foo):  0  0  0  0
                       var='':  1  0  0  0
                 var=([0]=''):  1  0  0  0
                 var=([1]=''):  1  1  0  0
                var=([1]=foo):  1  1  0  0
declare -A var; var=([x]=foo):  1  1  0  0
                       var=():  1  1  1  0
               declare -a var:  1  1  1  0
               declare -A var:  1  1  1  0
                    unset var:  1  1  1  1

Summary

  • declare -p var &>/dev/null is (100%?) reliable for testing named variables in Bash since at least 3.0.
  • [ -n "${var+x}" ] is reliable in POSIX compliant situations, but cannot handle arrays.
  • Other tests exist for checking if a variable is nonempty, and for checking for declared variables in other shells. But these tests are suited for neither Bash nor POSIX scripts.

Answered   2023-09-20 20:32:55

  • Of the answers I tried, this one (declare -p var &>/dev/null) is the ONLY one which works. THANK YOU! - anyone
  • Also, using the name of the variable without the $ prefix also works: declare -p PATH &> /dev/null returns 0 where as declare -p ASDFASDGFASKDF &> /dev/null returns 1. (on bash 5.0.11(1)-release) - anyone
  • Great work! 1 word of caution: declare var declares var a variable but it does not set it in terms of set -o nounset: Test with declare foo bar=; set -o nounset; echo $bar; echo $foo - anyone
  • @joehanna Thanks for catching that missing /! I fixed that, but I think the logic is confusing enough to warrant a function, especially in light of @xebeche's comment. @xebeche That's inconvenient for my answer.… It looks like I'll have to try something like declare -p "$1" | grep = or test -v "$1". - anyone
  • @xebeche After more testing, it seems that set -o nounset; (echo "$var") &>/dev/null has the same truth value as [ -n "${var+x}" ], so the only difference is set -o nounset causing a script/subshell to exit. That said, I agree that my answer should explain the difference between a variable being set vs merely declared instead of conflating these cases. Feel free to edit. I'm not returning to this answer until the next perennial notification. - anyone
if [ "$1" != "" ]; then
  echo \$1 is set
else
  echo \$1 is not set
fi

Although for arguments it is normally best to test $#, which is the number of arguments, in my opinion.

if [ $# -gt 0 ]; then
  echo \$1 is set
else
  echo \$1 is not set
fi

Answered   2023-09-20 20:32:55

  • The first test is backward; I think you want [ "$1" != "" ] (or [ -n "$1" ])... - anyone
  • This will fail if $1 is set to the empty string. - anyone
  • The second part of the answer (counting parameters) is a useful workaround for the first part of the answer not working when $1 is set to an empty string. - anyone
  • This will fail if set -o nounset is on. - anyone
  • Am I going crazy? Sourcing a script from bash with args [[ -n $1 ]] && FOO=$1 does not set FOO. [[ "$1" != "" ]] && FOO=$1 works ok. bash 4.2.46(2)-release (x86_64-redhat-linux-gnu) - anyone

On a modern version of Bash (4.2 or later I think; I don't know for sure), I would try this:

if [ ! -v SOMEVARIABLE ] #note the lack of a $ sigil
then
    echo "Variable is unset"
elif [ -z "$SOMEVARIABLE" ]
then
    echo "Variable is set to an empty string"
else
    echo "Variable is set to some string"
fi

Answered   2023-09-20 20:32:55

  • Also note that [ -v "$VARNAME" ] is not incorrect, but simply performs a different test. Suppose VARNAME=foo; then it checks if there is a variable named foo that is set. - anyone
  • Note for those wanting portability, -v is not POSIX compliant - anyone
  • If one gets [: -v: unexpected operator, one has to ensure to use bash instead of sh. - anyone

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.

Answered   2023-09-20 20:32:55

  • Looks like you accidentally included a "not" in your explanation of test -z. From test's man page: -z STRING returns true if the length of STRING is zero. So, test -z "${var-}" will return true if var is undefined or empty. - anyone

For those that are looking to check for unset or empty when in a script with set -u:

if [ -z "${var-}" ]; then
   echo "Must provide var environment variable. Exiting...."
   exit 1
fi

The regular [ -z "$var" ] check will fail with var; unbound variable if set -u but [ -z "${var-}" ] expands to empty string if var is unset without failing.

Answered   2023-09-20 20:32:55

  • You are missing a colon. It should be ${var:-}, not ${var-}. But in Bash, I can confirm it works even without the colon. - anyone
  • ${var:-} and ${var-} mean different things. ${var:-} will expand to empty string if var unset or null and ${var-} will expand to empty string only if unset. - anyone
  • Just learned something new, thanks! Omitting the colon results in a test only for a parameter that is unset. Put another way, if the colon is included, the operator tests for both parameter’s existence and that its value is not null; if the colon is omitted, the operator tests only for existence. It makes no difference here, but good to know. - anyone

You want to exit if it's unset

This worked for me. I wanted my script to exit with an error message if a parameter wasn't set.

#!/usr/bin/env bash

set -o errexit

# Get the value and empty validation check all in one
VER="${1:?You must pass a version of the format 0.0.0 as the only argument}"

This returns with an error when it's run

peek@peek:~$ ./setver.sh
./setver.sh: line 13: 1: You must pass a version of the format 0.0.0 as the only argument

Check only, no exit - Empty and Unset are INVALID

Try this option if you just want to check if the value set=VALID or unset/empty=INVALID.

TSET="good val"
TEMPTY=""
unset TUNSET

if [ "${TSET:-}" ]; then echo "VALID"; else echo "INVALID";fi
# VALID
if [ "${TEMPTY:-}" ]; then echo "VALID"; else echo "INVALID";fi
# INVALID
if [ "${TUNSET:-}" ]; then echo "VALID"; else echo "INVALID";fi
# INVALID

Or, even short tests ;-)

[ "${TSET:-}"   ] && echo "VALID" || echo "INVALID"
[ "${TEMPTY:-}" ] && echo "VALID" || echo "INVALID"
[ "${TUNSET:-}" ] && echo "VALID" || echo "INVALID"

Check only, no exit - Only empty is INVALID

And this is the answer to the question. Use this if you just want to check if the value set/empty=VALID or unset=INVALID.

NOTE, the "1" in "..-1}" is insignificant, it can be anything (like x)

TSET="good val"
TEMPTY=""
unset TUNSET

if [ "${TSET+1}" ]; then echo "VALID"; else echo "INVALID";fi
# VALID
if [ "${TEMPTY+1}" ]; then echo "VALID"; else echo "INVALID";fi
# VALID
if [ "${TUNSET+1}" ]; then echo "VALID"; else echo "INVALID";fi
# INVALID

Short tests

[ "${TSET+1}"   ] && echo "VALID" || echo "INVALID"
[ "${TEMPTY+1}" ] && echo "VALID" || echo "INVALID"
[ "${TUNSET+1}" ] && echo "VALID" || echo "INVALID"

I dedicate this answer to @mklement0 (comments) who challenged me to answer the question accurately.

Reference: 2.6.2 Parameter Expansion

Answered   2023-09-20 20:32:55

Read the "Parameter Expansion" section of the bash man page. Parameter expansion doesn't provide a general test for a variable being set, but there are several things you can do to a parameter if it isn't set.

For example:

function a {
    first_arg=${1-foo}
    # rest of the function
}

will set first_arg equal to $1 if it is assigned, otherwise it uses the value "foo". If a absolutely must take a single parameter, and no good default exists, you can exit with an error message when no parameter is given:

function a {
    : ${1?a must take a single argument}
    # rest of the function
}

(Note the use of : as a null command, which just expands the values of its arguments. We don't want to do anything with $1 in this example, just exit if it isn't set)

Answered   2023-09-20 20:32:55

  • I'm baffled that none of the other answers mention the simple and elegant : ${var?message}. - anyone

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       |
+----------------------+-------------+---------+------------+

Answered   2023-09-20 20:32:55

To check whether a variable is set with a non-empty value, use [ -n "$x" ], as others have already indicated.

Most of the time, it's a good idea to treat a variable that has an empty value in the same way as a variable that is unset. But you can distinguish the two if you need to: [ -n "${x+set}" ] ("${x+set}" expands to set if x is set and to the empty string if x is unset).

To check whether a parameter has been passed, test $#, which is the number of parameters passed to the function (or to the script, when not in a function) (see Paul's answer).

Answered   2023-09-20 20:32:55

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.

Answered   2023-09-20 20:32:55

  • Well done, Mike. It looks great. Like you said, it's not fully comprehensive, but it is quickly findable and comparable. - anyone

In Bash you can use -v inside the [[ ]] builtin:

#! /bin/bash -u

if [[ ! -v SOMEVAR ]]; then
    SOMEVAR='hello'
fi

echo $SOMEVAR

Answered   2023-09-20 20:32:55

If you are like me, what you were searching for was actually

"bash only run command if variable is set"

and you want that as a one-liner, so the following line is what you want

Only works on Bash 4.2 or later

Only run if set

if [[ -v mytest ]]; then echo "this runs only if variable is set"; fi

Only run if not set

if [[ ! -v mytest2 ]]; then echo "this runs only if variable is not set"; fi

Answered   2023-09-20 20:32:55

Using [[ -z "$var" ]] is the easiest way to know if a variable was set or not, but that option -z doesn't distinguish between an unset variable and a variable set to an empty string:

$ set=''
$ [[ -z "$set" ]] && echo "Set" || echo "Unset" 
Unset
$ [[ -z "$unset" ]] && echo "Set" || echo "Unset"
Unset

It's best to check it according to the type of variable: env variable, parameter or regular variable.

For a env variable:

[[ $(env | grep "varname=" | wc -l) -eq 1 ]] && echo "Set" || echo "Unset"

For a parameter (for example, to check existence of parameter $5):

[[ $# -ge 5 ]] && echo "Set" || echo "Unset"

For a regular variable (using an auxiliary function, to do it in an elegant way):

function declare_var {
   declare -p "$1" &> /dev/null
}
declare_var "var_name" && echo "Set" || echo "Unset"

Notes:

  • $#: gives you the number of positional parameters.
  • declare -p: gives you the definition of the variable passed as a parameter. If it exists, returns 0, if not, returns 1 and prints an error message.
  • &> /dev/null: suppresses output from declare -p without affecting its return code.

Answered   2023-09-20 20:32:55

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.

Answered   2023-09-20 20:32:55

You can do:

function a {
        if [ ! -z "$1" ]; then
                echo '$1 is set'
        fi
}

Answered   2023-09-20 20:32:55

  • Or you could do -n and not negate the -z. - anyone
  • you need quotes around $1, i.e., [ ! -z "$1" ]. Or you can write [[ ! -z $1 ]] in bash/ksh/zsh. - anyone
  • This will fail if $1 is set to the empty string. - anyone

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

Answered   2023-09-20 20:32:55

  • I'd like to know so much why this answer was downvoted... Works great for me. - anyone
  • In Bash, you could do the same without an eval: change eval "[ ! -z \${$1:-} ]" to indirect evaluation: [ ! -z ${!1:-} ];. - anyone
  • @BinaryZebra: Interesting idea. I suggest you post as a separate answer. - anyone
  • Using "eval" makes this solution vulnerable to code injection: $ is_var_defined 'x} ]; echo "gotcha" >&2; #' gotcha - anyone

My preferred way is this:

$ var=10
$ if ! ${var+false};then echo "is set";else echo "NOT set";fi
is set
$ unset -v var
$ if ! ${var+false};then echo "is set";else echo "NOT set";fi
NOT set

So basically, if a variable is set, it becomes "a negation of the resulting false" (what will be true = "is set").

And, if it is unset, it will become "a negation of the resulting true" (as the empty result evaluates to true) (so will end as being false = "NOT set").

Answered   2023-09-20 20:32:55

In a shell you can use the -z operator which is True if the length of string is zero.

A simple one-liner to set default MY_VAR if it's not set, otherwise optionally you can display the message:

[[ -z "$MY_VAR" ]] && MY_VAR="default"
[[ -z "$MY_VAR" ]] && MY_VAR="default" || echo "Variable already set."

Answered   2023-09-20 20:32:55

This is what I use every day:

#
# Check if a variable is set
#   param1  name of the variable
#
function is_set() { [[ $(eval echo "\${${1}+x}") ]]; }

This works well under Linux and Solaris down to Bash 3.0.

$ myvar="TEST"
$ is_set myvar ; echo $?
0

$ myvar=
$ is_set myvar ; echo $?
0

$ unset myvar
$ is_set myvar ; echo $?
1

Answered   2023-09-20 20:32:55

  • Since Bash 2.0+ Indirect expansion is available. You could replace the long and complex (with an eval also included) test -n "$(eval "echo "\${${1}+x}"")" for the equivalent: [[ ${!1+x} ]] - anyone
  • This is what I use too, eval is evil but this works with all the versions of bash and zsh I have had to use. - anyone
  • Is it possible to get rid of [[ and make it POSIX compliant? - anyone
  • Thank you. Based on your answer, I wrote a POSIX compliant version function. isset() { [ -n "$1" ] && [ -n "$(eval printf '%s' "\${$1+x}" || true)" ]; } It must be noted that, we should pass the variable NAME (without $) as text for checking. - anyone
[[ $foo ]]

Or

(( ${#foo} ))

Or

let ${#foo}

Or

declare -p foo

Answered   2023-09-20 20:32:55

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

Answered   2023-09-20 20:32:55

  • I know, but that's it. If something is empty you'll get an error! Sorry. :P - anyone
if [[ ${!xx[@]} ]] ; then echo xx is defined; fi

Answered   2023-09-20 20:32:55

  • Welcome to StackOverflow. While your answer is appreciated, it's best to give more than just code. Could you add reasoning to your answer or a small explanation? See this page for other ideas on expanding your answer. - anyone

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.

Answered   2023-09-20 20:32:55

  • won't work when you have set -u in effect which will print the unboud variable error - anyone