1840

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 ?
}
nbro
  • 12,226
  • 19
  • 85
  • 163
prosseek
  • 155,475
  • 189
  • 518
  • 818
  • 14
    `if test $# -gt 0; then printf 'arg \n' "$@"; fi`. – Jens Jul 09 '13 at 16:57
  • 238
    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. – Nathan Kidd Nov 29 '13 at 17:56
  • 9
    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. – Graeme Jan 28 '14 at 17:58
  • 5
    As pointed out by @NathanKidd, correct solutions are given by Lionel and Jens. prosseek, you should [switch your accepted answer](http://meta.stackexchange.com/questions/62252/is-it-poor-form-to-switch-accepted-answers) to one of these. – Garrett Feb 13 '14 at 23:22
  • 3
    ... or the incorrect answer could be downvoted by the more discerning among us, since @prosseek is not addressing the problem. – dan3 Jul 15 '14 at 09:46
  • I often use parameter substitution with `${1?"Usage: func_name first_arg"}`, as mentioned in two answers (http://stackoverflow.com/a/9087674/116891 and another) below, because it will halt the script for me and print out a helpful message. – Pat Jan 30 '15 at 20:18
  • 1
    @user, if requiring Bash 4.2 is ok than that answer looks good, but for most people I doubt having to add Bash version checks is the better way. – Nathan Kidd Apr 08 '16 at 14:51
  • A discussion of how to do that with multiple variables is made at http://stackoverflow.com/q/27291287/873282 – koppor Mar 28 '17 at 12:43
  • As stated in the comment https://stackoverflow.com/a/17538964/2169092 , using if [ -v foo ] ; then ... directly checks if $foo is set. Note: the missing $ in [ -v foo ] is correct. – danba Feb 26 '18 at 11:42
  • Related post: [Test for non-zero length string in Bash: -n “$var” vs “$var”](https://stackoverflow.com/a/49825114/6862601). – codeforester Jul 11 '18 at 20:33
  • Another simple, but useful tip is `which {varname}` (when in terminal/command line). This will tell you if a command exists with the same name. Good to know under some circumstances so you don't accidentally call a program. – MrPotatoHead Oct 06 '20 at 01:03

37 Answers37

2669

(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.

Benjamin Toueg
  • 9,289
  • 7
  • 39
  • 69
Lionel
  • 27,268
  • 1
  • 13
  • 5
  • 35
    @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" ]`. – Graeme Feb 13 '14 at 22:08
  • 12
    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). – Brent212 Sep 09 '14 at 22:04
  • This solution also works if you have made bash strict by using 'set u' to cause an error on undefined variables – mat_geek Oct 23 '14 at 23:20
  • 13
    For the solution's syntax ${parameter+word}, the official manual section is http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion ; 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 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 (good to know)has much clearer docs here. – Destiny Architect Nov 08 '14 at 15:04
  • 8
    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. – FauxFaux Jun 11 '15 at 11:32
  • 1
    Just a word of caution on this. If I have a script (tmp.sh) with this answer: `if [ -z ${var+x} ]; then echo "var is unset"; else echo "var is set to '$var'"; fi` and run it as `var=sneaky ./tmp.sh` the var will be set to sneaky in the script. This is a great option but it does open up a possible security issue when using this type of parameter expansion notation. – jdf Jul 10 '15 at 23:29
  • 1
    How is it a security issue? If the script isn't expecting a value for `var` from outside the script, then it would make an absolute assignment at the beginning, such as `var=foo`. Then it wouldn't be possible to pass a value into the script. – chepner Sep 12 '15 at 13:26
  • 1
    how to do this for arguments aka. $1 – Arnold Roa Nov 30 '15 at 15:53
  • 5
    The code: [ -z ${var+x} ] is subjected to shell expansion on the "x". If IFS happens to be x (IFS=x), then the test fails. Simple quoting: `[ -z "${var+x}" ]` solves the problem. –  Dec 01 '15 at 02:01
  • `` { [ -z ${var+x} ] && echo "no val" ; } || echo " set" ksh: test: 0403-004 Specify a parameter with this command. set`` This does not work with single Square bracket ( old posix version ). It will work this way ` { [[ -z ${var+x} ]] && echo "no val" ; } || echo " set" ` this is the modern version of the old '[' test – user1874594 Feb 13 '16 at 02:50
  • 53
    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. – nshew13 May 05 '16 at 20:09
  • 1
    Specifically regarding this answer, is it better to use `[[` & `]]` or `[` & `]`? – kevinarpe Jun 06 '16 at 02:29
  • 1
    according to the docs provided, there really isn't a right or wrong way to do this. it just depends on your intent within the given context. interesting to see @jdf found a race condition of a TOCTOU event. I would assume this is true for most shell scripts though. not necessarily just in this context. –  Oct 24 '16 at 02:51
  • 1
    @ArnoldRoa to check for an argument just use a `1` in place of `var`. So... `[ -z ${1+x} ]` – Bruno Bronosky Mar 24 '17 at 03:54
  • 6
    NOTE: This IS compatible with `set -o nounset` aka `set -u` which you should be using along with `set -o errexit` aka `set -e` or combined `set -eu`. – Bruno Bronosky Mar 24 '17 at 03:57
  • @Lionel you say "it would sometimes be better to write the solution with quotes" but you don't say why it would be better. readabilty? robustness? some special case that does/doesn't work? – simpleuser Apr 14 '17 at 07:02
  • 1
    The reason [ -z $var ] is wrong is because it doesn't provide the test of "if a variable is set". It provides the test of "if a variable is set and non-empty". It's common in shell programming to treat the two as equivalent but it's nevertheless a different test. As for quotes: it's generally a good idea to quote every parameter expansion unless you **know** you want word-splitting and file globbing. Those features are like a trap primed to break your script as soon as you hit that edge case. But as previously noted, in this case quoting doesn't matter. – tetsujin May 26 '17 at 16:44
  • Today I saw for the first time the very simple syntax if [ $MYVAR ]; then .. (in other words, just test the variable, no -n or -z operators). That seems to work but looks wrong, please comment. – chrisinmtown Jun 15 '17 at 09:48
  • 1
    @chrisinmtown That confuses set-to-"" with unset, and can also fail bizarrely if the variable contains spaces or shell wildcards. Unquoted variable references in shell scripts are almost always a mistake. – Gordon Davisson Oct 12 '17 at 19:09
  • 2
    Is there a clean way to flip the logic of this? The conditional checks the inverse. – Nick T Jan 05 '18 at 21:37
  • @GordonDavisson The opposite of `-z` is `-n`, so it's `[ -n "${foo+x}" ]` to test for "set, maybe empty". Note that I suggest the quotes, as this makes it more easy to add more tests, like `[ -n "${foo+x}" -a -n "${baz+x}" ]` (which also can be written as `[ -n "${foo+${baz+x}}" ]`). Note that `[ -n "${foo+x}" -o -n "${baz+x}" ]` can be shortened to `[ -n "${foo+x}${baz+x}" ]`. – Tino Mar 06 '18 at 10:30
  • @Dagrooms This is not particular to `bash`. Both `[ .. ]` for test and `${var+x}` are standard Bourne Shell builtins. So you have to blame [Stephen R. Bourne](https://en.wikipedia.org/wiki/Stephen_R._Bourne) for inventing it. `bash` just follows the standard. – Tino Mar 06 '18 at 10:44
  • I'm getting bad substitution error. Perhaps it would useful to include in the answer WHY there is a substitution in a check if a variable is set – Seph Jun 10 '18 at 08:10
  • 4
    why the `-z`? Doesn't just `[ ${var+x} ]` answer the question? – n.caillou Sep 06 '18 at 01:20
  • It's better to officially test the existence of a variable, rather than testing based on its value. See Russell Harmon's answer. – Edward Ned Harvey Jan 18 '19 at 15:14
  • 2
    A neat (but possibly "too cute") trick with this is to use the replacement string to form a true/false result: `if ${var+false} true; then echo "var is unset"; else echo "var is set to '$var'"; fi` (definitely without quotes in the expansion), so that `true` is either a command or an (ignored) argument to the `false` command. – Toby Speight Jan 30 '19 at 17:43
  • 3
    This is the best POSIX sh answer. But it fails for arrays (a Bash feature since at least 3.0) with position 0 unset. E.g., `var=(); if [ -z ${var+x} ]; then echo "unset"; else echo "set"; fi` will echo `unset` while `declare -p var` returns 0 and outputs `declare -a var=()`. Full explanation in my answer below. – Mark Haferkamp Apr 10 '19 at 11:25
  • The advice to "distinguish between a variable that is unset and a variable that is set to the empty string" in the unix shell is a terrible advice. Nobody in real world would imply that as it is uncommon in shell to do so. So I would easy do this `SOMEVAR='' your_script.sh` to unset some variable in the current environment and got screwed. If you want to follow POLA you must never distinguish between unset and empty variable in shell. – user4674453 Apr 26 '19 at 09:16
  • how to make this work for say, you have 4 possible arguments. You want to check if argument 4 is set. – Toskan May 03 '19 at 21:47
  • @N13 **!DANGER!** You cannot omit quotes in `[ -z "$var" ]`. Compare: `bash -c 'x="a -o -n b"; [ -z "$x" ] && echo empty || echo set'` (prints `set`) vs. `bash -c 'x="a -o -n b"; [ -z $x ] && echo empty || echo set'` (prints `empty`) vs. `bash -c 'x=$'\n'; [ -z $x ] && echo empty || echo set'` (prints `empty`) vs. `bash -c 'x=" -o -n b"; [ -z $x ] && echo empty || echo set'` (prints some ugly error to stderr additionally to `set`) – Tino May 20 '19 at 16:27
  • @AlexanderMills Look at the bottom of the table here, which explains this: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 – Adam Erickson May 24 '19 at 22:59
  • It's unsafe to use the `[ -z "${var}" ]` construct if `set -eu` is set, `bash` will dereference the variable to fill out the quotes before `-z` is evaluated, triggering script failure. – Michael Mol Apr 08 '20 at 19:33
  • i'm not sure i understand the purpose of the +x in here, i mean `[ -z "$var" ]` also seems to work – Fuseteam Aug 03 '20 at 12:29
  • For those who run `[-z 'foo'] && echo bar || echo baz` and get `bad pattern: [-z` error: add spaces before and after square brackets, like this: `[ -z 'foo' ] && echo bar || echo baz` – Mikhail Vasin Nov 01 '20 at 18:14
  • @Fuseteam, the purpose of `${var+x}` is to make an empty-but-set variable distinct from an unset one. `[ -z "$var" ]` treats the cases as identical. – Charles Duffy Nov 27 '20 at 03:16
  • @CharlesDuffy so it checks specifically for a empty variable? but if the question is to check if a variable is set what makes `[ -z "$var" ]` wrong? would `[ -z ${var+x} ]` return true for an empty variable? – Fuseteam Dec 01 '20 at 16:32
  • 1
    @Fuseteam, `[ -z ${var+x} ]`'s quoting is wrong (`[ -z ]` is treated like `[ -n -z ]`, which is a test of whether the string `-z` is non-empty, not a test of whether `${var+x}` expands to any particular value). It should be `[ -z "${var+x}" ]`, which will return true for a set-but-empty variable, true for a set-and-nonempty variable, and false for an unset variable. Even if the outcome of both tests are the same, using the right syntax makes the mechanism easier to reason about. – Charles Duffy Dec 01 '20 at 16:42
  • ah cool now i get it so `[ -z "$var" ]` would miss if a variable is set but empty – Fuseteam Dec 01 '20 at 16:44
  • This doesn't work in scripts where `set -u` is used. :( – Bastian Voigt Jan 06 '21 at 12:02
  • I think this great answer should be updated to clarify what @DestinyArchitect said about the colon. I needed the colon in my script because a variable was being set to NULL by the result of a command. – FlexMcMurphy Jan 29 '21 at 16:56
  • In a vast majority of cases it really doesn't matter whether the string is an empty one or doesn't exist at all. It makes no sense to focus on such a sophisticated distinction. – Wojciech Marciniak Feb 26 '21 at 22:45
1036

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
Joshua Pinter
  • 37,288
  • 19
  • 208
  • 218
mbrannig
  • 10,669
  • 1
  • 12
  • 3
  • 93
    I usually prefer `[[ ]]` over `[ ]`, as `[[ ]]` is more powerfull and causes less problems in certain situations (see [this question](http://stackoverflow.com/questions/3427872/whats-the-difference-between-and-in-bash) for an explanation of the difference between the two). The question specifically asks for a *bash* solution and doesn't mention any portability requirements. – Flow Oct 13 '13 at 15:26
  • 20
    The question is how one can see if a variable is *set*. Then you can't assume that the variable *is* set. – HelloGoodbye Nov 12 '13 at 13:15
  • 8
    I agree, `[]` works better especially for shell (non-bash) scripts. – Hengjie Jun 02 '14 at 13:09
  • 74
    Note that `-n` is the default test, so more simply `[ "$1" ]` or `[[ $1 ]]` work as well. Also note that with `[[ ]]` [quoting is unnecessary](http://wiki.bash-hackers.org/syntax/ccmd/conditional_expression#word_splitting). – tne Nov 26 '14 at 13:49
  • 2
    on CentOS undet tcsh, `if [ -n "$BASH" ] ; then echo y; else echo n; fi` says "BASH: Undefined variable" – 18446744073709551615 Apr 16 '18 at 10:12
  • 2
    Seems it's not really opposite... Real opposite is `if [ ! -z "$1" ]` – The Godfather Aug 16 '18 at 20:34
  • 1
    It's better to officially test the existence of a variable, rather than testing based on its value. See Russell Harmon's answer. – Edward Ned Harvey Jan 18 '19 at 15:14
  • 1
    @Flow `[[ ]]` works in bash. But `[ ]` works in bash, in sh, in ash, in dash and it should also work in shells not familiar with that syntax at all, as there should be `/bin/[` and it should be a symlink to `/bin/test` and test accepts exactly the same syntax as `[ ]` does. So if you want a script that starts with `#!/bin/sh`, you must not use `[[ ]]`, if you use that, your script must be `#!/bin/bash` and can only work on systems with bash installed. – Mecki Apr 26 '19 at 15:21
  • Right, that is why I wrote "The question specifically asks for a bash solution and doesn't mention any portability requirements". – Flow Apr 26 '19 at 15:35
574

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       |  FOO="world"         |     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           | ""              |
+--------------------+----------------------+-----------------+-----------------+
Jens
  • 61,963
  • 14
  • 104
  • 160
  • This doesn't work if the parameter is `1`, i.e. if you want to check whether the first argument to a script is set or not. – HelloGoodbye Nov 26 '13 at 14:06
  • 5
    @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 ... – Jens Nov 27 '13 at 13:07
  • How can the first example echo "foo"? How does "foo" come in to `$1`? – HelloGoodbye Nov 27 '13 at 20:38
  • Anyway, the question was how to tell whether a variable is set or not, not how to tell whether it is set but null or unset. So I guess you would want to use the `${parameter+word}` syntax for that. This is on the other hand what I tried but couldn't get to work. – HelloGoodbye Nov 27 '13 at 20:42
  • 5
    @HelloGoodbye The positional parameters can be set with, uh, `set` :-) – Jens Nov 28 '13 at 07:59
  • 114
    This answer is very confusing. Any practical examples on how to use this table? – Ben Davis Sep 27 '14 at 17:16
  • 13
    @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. – Jens Mar 10 '15 at 11:08
  • 2
    `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}` – TrinitronX Jan 19 '16 at 23:30
  • Expanding on @Jens answer: `if [ -z "${foo+bar}" ];then echo "foo is unset"; else if [ -z "${foo-bar}" ];then echo "foo is set to null"; else echo "foo is set to $foo";fi;fi` – Eric Aug 27 '16 at 11:56
  • What is the difference between `substitute` and `assign`? – Eugen Konkov Mar 04 '18 at 12:45
  • @EugenKonkov Assigning changes the parameter's value, substitution does not (only expands the word). – Jens Mar 04 '18 at 16:29
  • 1
    Nice work, Jens. I always love the POSIX version answer although the OP already limits it to Bash. – gpanda Oct 26 '18 at 11:27
  • 1
    It's better to officially test the existence of a variable, rather than testing based on its value. See Russell Harmon's answer. – Edward Ned Harvey Jan 18 '19 at 14:34
  • wonderfull answer, very complete and clear, thanks a lot ! – herve-guerin Oct 27 '20 at 21:50
318

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.

Mark Amery
  • 110,735
  • 57
  • 354
  • 402
eatnumber1
  • 3,388
  • 1
  • 10
  • 6
  • 11
    In bash 4.1.2, regardless of whether variable is set, `[[ -v aaa ]]; echo $?` ==> `-bash: conditional binary operator expected` `-bash: syntax error near 'aaa'` – Dan Oct 31 '13 at 09:52
  • 37
    The '-v' argument to the 'test' builtin was added in bash 4.2. – Ti Strga Jan 13 '14 at 17:19
  • 19
    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. – osirisgothra Jul 14 '14 at 10:59
  • 1
    I think osirisgothra's remark about using $# for checking the positional parameters should be added to the answer. Unfortunately -v doesn't work with positional parameters (e.g. [[ -v 2 ]] is not the same as [[ $# -ge 2 ]]), at least in my bash 4.3.11. – Jaan Sep 02 '16 at 23:40
  • 3
    Note this does not work for variables that are declared but unset. e.g. `declare -a foo` – miken32 Jun 19 '18 at 23:26
  • 33
    This is why I keep reading after the accepted/highest-voted answer. – Joe Jul 24 '18 at 20:16
  • 2
    This is the preferred solution when the name of the variable is in another variable and you want to use [the bash "strict mode"](http://redsymbol.net/articles/unofficial-bash-strict-mode/). So `[[ -v "${VARNAME:-}" ]]` will behave correctly in all 3 cases: 1) VARNAME is unset, 2) it's set to the name of an unset variable and 3) it's set to the name of a set variable. – Ville Oikarinen Feb 21 '19 at 13:19
  • 1
    This fails with arrays that don't have position 0 set. Example: `foo[1]=bar; [[ -v foo ]]` returns 1. But `declare -p foo` works. Explanation and example tests in my answer below. – Mark Haferkamp Apr 10 '19 at 11:13
  • 2
    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 ]]` – Amit Naidu Jul 23 '19 at 19:34
  • @MarkHaferkamp the `declare -p` way gives false positive for a variable that has been declared, but not set. For an array to check, if it has at least one element set, you could use `[[ -v foo[*] ]]`. It works with regular variable, as well. – jarno Dec 25 '19 at 18:11
  • @jarno `[[ -v foo[*] ]]` gives a false negative for a set empty array, as in `foo=()`. Unfortunately, I don't know a good way to differentiate between a variable being _set_ vs merely _declared_. – Mark Haferkamp Jan 07 '20 at 09:15
  • Because of this post I just found out that macOS comes with an very old version of bash (version `3.2`). I found a nice article [here](https://itnext.io/upgrading-bash-on-macos-7138bd1066ba) which describes how to update to the newest version. – winklerrr Sep 01 '20 at 12:49
  • this should be top answer – Gostega Jan 28 '21 at 08:37
184

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

Steel Brain
  • 3,774
  • 25
  • 38
ennuikiller
  • 43,779
  • 13
  • 108
  • 136
  • I think this must be faster than the common [ x"$var" = x ]. Just wanted to point that out. There are numerous solutions but for the crazy people using bash for a substitute of perl, that matters. – akostadinov Nov 28 '12 at 14:47
  • 50
    There is a difference between an unset parameter and a parameter with a null value. – chepner Mar 11 '13 at 18:14
  • 25
    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. – Philluminati Jun 03 '13 at 13:06
  • 48
    `[[ ]]` is nothing but a portability pitfall. `if [ -n "$1" ]` should be used here. – Jens Jul 09 '13 at 16:55
  • 75
    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`. – Keith Thompson Aug 05 '13 at 19:13
  • 4
    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](http://stackoverflow.com/a/3601734/194894) is correct. But if you want to check if the variable is declared but not initialized (i.e `foo=`), then [Russel's answer](http://stackoverflow.com/a/17538964/194894) is correct. – Flow Oct 13 '13 at 15:38
  • 1
    @Kyrol `if [[ $1 = ""]]; then echo '$1 is blank :('; else echo '$1 is filled up'; fi` –  Nov 18 '13 at 14:39
  • 1
    @Flow "set" has a specific definition, that is why the command to remove a variable (not just make it null) is `unset`. "Null" is the proper term to denote a variable that is set, but is empty (i.e. zero-length). – TTT Apr 24 '14 at 16:45
  • 2
    Won't this fail under `set -u`? – jpmc26 May 06 '14 at 03:41
  • It's better to officially test the existence of a variable, rather than testing based on its value. See Russell Harmon's answer. – Edward Ned Harvey Jan 18 '19 at 14:34
102

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

   +----------------------+------------+-----------------------+-----------------------+
   |   if VARIABLE is:    |    set     |         empty         |        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".
deizel
  • 10,234
  • 1
  • 37
  • 48
  • 1
    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. – Palec Jun 18 '17 at 09:37
  • 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. – Astronaut May 20 '20 at 17:52
  • 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. – Tom Nov 09 '20 at 04:02
  • 1
    Per the [documentation](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02), 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. – Tom Nov 09 '20 at 04:13
  • @Tom Yes, that is correct - I've edited my answer as you suggest to hopefully make things clearer. – deizel Nov 10 '20 at 16:03
  • @Astronaut I had missed your comment but it sounds like your script has `errexit` set, which is masking the real exit status. – deizel Nov 26 '20 at 02:12
67

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
phkoester
  • 711
  • 5
  • 7
  • I too have been using this for a while; just in a reduced way: `[ $isMac ] && param="--enable-shared"` –  Nov 30 '14 at 11:39
  • @Bhaskar I think it's because [this answer](https://stackoverflow.com/a/13864829/619001) 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. – Mark Haferkamp Feb 16 '18 at 08:06
35

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
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Seamus Connor
  • 1,677
  • 1
  • 17
  • 24
  • 5
    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. – chepner Nov 08 '14 at 16:46
  • 5
    Note for those wanting portability, `-v` is not POSIX compliant – kzh Jan 02 '15 at 04:40
  • If one gets `[: -v: unexpected operator`, one has to ensure to use `bash` instead of `sh`. – koppor Mar 28 '17 at 12:26
31

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.
Mark Haferkamp
  • 583
  • 5
  • 9
  • 3
    Of the answers I tried, this one (`declare -p var &>/dev/null`) is the ONLY one which works. THANK YOU! – user1387866 Mar 14 '19 at 17:18
  • 2
    @mark-haferkamp I tried to edit your answer to add the critical preceding "/" to your function code so it reads `... &> /dev/null` but SO would not let me make a single character edit (must be >6 characters - even tried adding white-space but it didn't work). Also, might be worth changing the function to switch out `$1` for a sample named variable (eg `OUTPUT_DIR`) since as you pointed out, special variables like `$1` do not work here. Anyway, it is a critical edit otherwise the code is looking to create a file called `null` in a relative directory called `dev`. BTW - great answer! – joehanna Nov 23 '19 at 20:05
  • 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) – joehanna Nov 23 '19 at 20:10
  • 1
    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` – xebeche Nov 25 '19 at 22:54
  • @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"`. – Mark Haferkamp Dec 02 '19 at 06:09
  • @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. – Mark Haferkamp Dec 02 '19 at 06:52
27
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
Paul Creasey
  • 26,917
  • 10
  • 51
  • 88
21

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.

RubenLaguna
  • 15,227
  • 11
  • 82
  • 96
  • You are missing a colon. It should be `${var:-}`, not `${var-}`. But in Bash, I can confirm it works even without the colon. – Palec Nov 16 '16 at 14:27
  • 5
    `${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. – RubenLaguna Nov 16 '16 at 14:38
  • 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. – Palec Nov 16 '16 at 15:01
21

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 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02

Jarrod Chesney
  • 629
  • 6
  • 5
16

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)

chepner
  • 389,128
  • 51
  • 403
  • 529
  • 1
    I'm baffled that none of the other answers mention the simple and elegant `: ${var?message}`. – tripleee Sep 12 '15 at 06:33
  • From where did you take this? Wonderful! It justa works! And it's short and elegant! Thank you! – Roger Sep 23 '19 at 09:39
15

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).

Community
  • 1
  • 1
Gilles 'SO- stop being evil'
  • 92,660
  • 35
  • 189
  • 229
14

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

#! /bin/bash -u

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

echo $SOMEVAR
AlejandroVD
  • 991
  • 10
  • 17
10

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.
Mark Haferkamp
  • 583
  • 5
  • 9
Peregring-lk
  • 9,237
  • 6
  • 37
  • 86
9

Summary

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

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

    if test ! -z "${var+}"; then
      echo "name is set to $name"
    else
      echo "name 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 $a 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.

infokiller
  • 2,836
  • 1
  • 18
  • 26
7

To clearly answer 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       |
+----------------------+-------------+---------+------------+
Cameron Hudson
  • 1,573
  • 1
  • 14
  • 18
5

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

Community
  • 1
  • 1
kevinarpe
  • 17,685
  • 21
  • 107
  • 133
4

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").

jarno
  • 621
  • 9
  • 21
Aquarius Power
  • 3,212
  • 5
  • 26
  • 56
4

You can do:

function a {
        if [ ! -z "$1" ]; then
                echo '$1 is set'
        fi
}
codaddict
  • 410,890
  • 80
  • 476
  • 515
2

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.

bash-3.00$ myvar="TEST"
bash-3.00$ is_set myvar ; echo $?
0
bash-3.00$ myvar=
bash-3.00$ is_set myvar ; echo $?
0
bash-3.00$ unset myvar
bash-3.00$ is_set myvar ; echo $?
1
Jonathan H
  • 6,750
  • 5
  • 39
  • 68
fr00tyl00p
  • 317
  • 2
  • 10
  • 1
    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} ]]` –  Nov 29 '15 at 20:26
  • 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. – Jonathan H Mar 05 '21 at 21:24
1
[[ $foo ]]

Or

(( ${#foo} ))

Or

let ${#foo}

Or

declare -p foo
Steven Penny
  • 82,115
  • 47
  • 308
  • 348
1

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."
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
kenorb
  • 118,428
  • 63
  • 588
  • 624
1

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
printf "$R" "\${FOO:?x}" "${S:?x}" "<error>" "<error>  "; S='V';N='';unset U
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" "----------" "-------" "-------" "---------";

# For reference, the following two lines are the literal rows of the table
# which would cause the script to exit and so have had <error> written in.
#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

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.

Mike Clark
  • 9,026
  • 2
  • 35
  • 49
0
if [[ ${1:+isset} ]]
then echo "It was set and not null." >&2
else echo "It was not set or it was null." >&2
fi

if [[ ${1+isset} ]]
then echo "It was set but might be null." >&2
else echo "It was was not set." >&2
fi
solidsnack
  • 1,443
  • 14
  • 21
  • `$1`, `$2` and up to `$N` is always set. Sorry, this is fail. –  Nov 18 '13 at 14:45
  • I tried it, and it didn't work, maybe my bash version lacks support for that. Idk, sorry if I'm wrong. :) –  Nov 20 '13 at 18:53
0

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
  • I know, but that's it. If something is empty you'll get an error! Sorry. :P –  Nov 30 '13 at 16:47
0
if [[ ${!xx[@]} ]] ; then echo xx is defined; fi
Keith Thompson
  • 230,326
  • 38
  • 368
  • 578
Graham
  • 1
  • 11
    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](http://stackoverflow.com/help/how-to-answer) for other ideas on expanding your answer. – Celeo Feb 11 '15 at 20:58
0

I like auxiliary functions to hide the crude details of bash. In this case, doing so adds even more (hidden) crudeness:

# The first ! negates the result (can't use -n to achieve this)
# the second ! expands the content of varname (can't do ${$varname})
function IsDeclared_Tricky
{
  local varname="$1"
  ! [ -z ${!varname+x} ]
}

Because I first had bugs in this implementation (inspired by the answers of Jens and Lionel), I came up with a different solution:

# Ask for the properties of the variable - fails if not declared
function IsDeclared()
{
  declare -p $1 &>/dev/null
}

I find it to be more straight-forward, more bashy and easier to understand/remember. Test case shows it is equivalent:

function main()
{
  declare -i xyz
  local foo
  local bar=
  local baz=''

  IsDeclared_Tricky xyz; echo "IsDeclared_Tricky xyz: $?"
  IsDeclared_Tricky foo; echo "IsDeclared_Tricky foo: $?"
  IsDeclared_Tricky bar; echo "IsDeclared_Tricky bar: $?"
  IsDeclared_Tricky baz; echo "IsDeclared_Tricky baz: $?"

  IsDeclared xyz; echo "IsDeclared xyz: $?"
  IsDeclared foo; echo "IsDeclared foo: $?"
  IsDeclared bar; echo "IsDeclared bar: $?"
  IsDeclared baz; echo "IsDeclared baz: $?"
}

main

The test case also shows that local var does NOT declare var (unless followed by '='). For quite some time I thought i declared variables this way, just to discover now that i merely expressed my intention... It's a no-op, i guess.

IsDeclared_Tricky xyz: 1
IsDeclared_Tricky foo: 1
IsDeclared_Tricky bar: 0
IsDeclared_Tricky baz: 0
IsDeclared xyz: 1
IsDeclared foo: 1
IsDeclared bar: 0
IsDeclared baz: 0

BONUS: usecase

I mostly use this test to give (and return) parameters to functions in a somewhat "elegant" and safe way (almost resembling an interface...):

#auxiliary functions
function die()
{
  echo "Error: $1"; exit 1
}

function assertVariableDeclared()
{
  IsDeclared "$1" || die "variable not declared: $1"
}

function expectVariables()
{
  while (( $# > 0 )); do
    assertVariableDeclared $1; shift
  done
}

# actual example
function exampleFunction()
{
  expectVariables inputStr outputStr
  outputStr="$inputStr world!"
}

function bonus()
{
  local inputStr='Hello'
  local outputStr= # remove this to trigger error
  exampleFunction
  echo $outputStr
}

bonus

If called with all requires variables declared:

Hello world!

else:

Error: variable not declared: outputStr

Daniel S
  • 408
  • 3
  • 16
0

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.

Hatem Jaber
  • 2,206
  • 2
  • 19
  • 38
-1

To check if a var is set or not

var=""; [[ $var ]] && echo "set" || echo "not set"
koola
  • 1,302
  • 1
  • 10
  • 15
-1

Functions to check if variable is declared/unset

including empty $array=()


The following functions test if the given name exists as a variable

# The first parameter needs to be the name of the variable to be checked.
# (See example below)

var_is_declared() {
    { [[ -n ${!1+anything} ]] || declare -p $1 &>/dev/null;}
}

var_is_unset() {
    { [[ -z ${!1+anything} ]] && ! declare -p $1 &>/dev/null;} 
}
  • By first testing if the variable is (un)set, the call to declare can be avoided, if not necessary.
  • If however $1 contains the name of an empty $array=(), the call to declare would make sure we get the right result
  • There's never much data passed to /dev/null as declare is only called if either the variable is unset or an empty array.

This functions would test as showed in the following conditions:

a;       # is not declared
a=;      # is declared
a="foo"; # is declared
a=();    # is declared
a=("");  # is declared
unset a; # is not declared

a;       # is unset
a=;      # is not unset
a="foo"; # is not unset
a=();    # is not unset
a=("");  # is not unset
unset a; # is unset

.

For more details

and a test script see my answer to the question "How do I check if a variable exists in bash?".

Remark: The similar usage of declare -p, as it is also shown by Peregring-lk's answer, is truly coincidental. Otherwise I would of course have credited it!

Community
  • 1
  • 1
Martin Rüegg
  • 702
  • 8
  • 13
-1

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
Christopher King
  • 858
  • 1
  • 6
  • 13
-1

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 recommand its usage, but this implies that the difference between an unset variable and a variable set with an empty string is handled correctly.

-2

If you wish to test that a variable is bound or unbound, this works well, even after you've turned on the nounset option:

set -o noun set

if printenv variableName >/dev/null; then
    # variable is bound to a value
else
    # variable is unbound
fi
user1857592
  • 5
  • 1
  • 2
  • 1
    I think you mean `set -o nounset`, not `set -o noun set`. This only works for variables that have been `export`ed. It also changes your settings in a way that's awkward to undo. – Keith Thompson Aug 05 '13 at 19:10
-2

I always use this one, based on the fact that it seems easy to be understood by anybody who sees the code for the very first time:

if [ "$variable" = "" ]
    then
    echo "Variable X is empty"
fi

And, if wanting to check if not empty;

if [ ! "$variable" = "" ]
    then
    echo "Variable X is not empty"
fi

That's it.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
mijnnaam
  • 17
  • 1
  • 4
    The question asks how to check if a variable is set, not if a variable is empty. – HelloGoodbye Nov 27 '13 at 20:45
  • You probably need to use `[[ ... ]]` instead of `[ ... ]`. The latter is actually an external command and wouldn't have any visibility into whether a variable is set or not. – solidsnack Nov 29 '13 at 22:41
-3
case "$1" in
 "") echo "blank";;
 *) echo "set"
esac
ghostdog74
  • 286,686
  • 52
  • 238
  • 332