How to tell if a string is not defined in a Bash shell script
Asked Answered
I

12

169

If I want to check for the null string I would do

[ -z $mystr ]

but what if I want to check whether the variable has been defined at all? Or is there no distinction in Bash scripting?

Inexact answered 23/10, 2008 at 4:26 Comment(7)
please be in the habit of using [ -z "$mystr" ] as opposed to [ -z $mystr ]Valeric
how to do the inverse thing? I mean when the string is not nullHesitation
@flow: What about [ -n "${VAR+x}"] && echo not nullParodist
@CharlesDuffy I've read this before in a lot of online resources. Why is this preferred ?Murray
@Ayos because if you don't have quotes, the content of the variable is string-split and globbed; if it's an empty string, it becomes [ -z ] instead of [ -z "" ]; if it has spaces, it becomes [ -z "my" "test" ] instead of [ -z my test ]; and if it's [ -z * ], then the * is replaced with the names of files in your directory.Valeric
@CharlesDuffy since the question referred to Bash, maybe someone should mention that if you use [[ -z $mystr ]] no splitting or globbing will be done. (I leave the argument about whether you should still quote for this or that reason to someone else, but I believe splitting and globbing would no longer be an issue.)Emission
@GarretWilson, indeed, it's safe not to quote when using [[ in the manner you show. (There are other cases where quoting can be needed in [[, but they're in combination with operators like = or =~)Valeric
D
154

I think the answer you are after is implied (if not stated) by Vinko's answer, though it is not spelled out simply. To distinguish whether VAR is set but empty or not set, you can use:

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

You probably can combine the two tests on the second line into one with:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

However, if you read the documentation for Autoconf, you'll find that they do not recommend combining terms with '-a' and do recommend using separate simple tests combined with &&. I've not encountered a system where there is a problem; that doesn't mean they didn't used to exist (but they are probably extremely rare these days, even if they weren't as rare in the distant past).

You can find the details of these, and other related shell parameter expansions, the test or [ command and conditional expressions in the Bash manual.


I was recently asked by email about this answer with the question:

You use two tests, and I understand the second one well, but not the first one. More precisely I don't understand the need for variable expansion

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi

Wouldn't this accomplish the same?

if [ -z "${VAR}" ]; then echo "VAR is not set at all"; fi

Fair question - the answer is 'No, your simpler alternative does not do the same thing'.

Suppose I write this before your test:

VAR=

Your test will say "VAR is not set at all", but mine will say (by implication because it echoes nothing) "VAR is set but its value might be empty". Try this script:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo "JL:1 VAR is not set at all"; fi
if [ -z "${VAR}" ];     then echo "MP:1 VAR is not set at all"; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo "JL:2 VAR is not set at all"; fi
if [ -z "${VAR}" ];     then echo "MP:2 VAR is not set at all"; fi
)

The output is:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

In the second pair of tests, the variable is set, but it is set to the empty value. This is the distinction that the ${VAR=value} and ${VAR:=value} notations make. Ditto for ${VAR-value} and ${VAR:-value}, and ${VAR+value} and ${VAR:+value}, and so on.


As Gili points out in his answer, if you run bash with the set -o nounset option, then the basic answer above fails with unbound variable. It is easily remedied:

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

Or you could cancel the set -o nounset option with set +u (set -u being equivalent to set -o nounset).

Dissection answered 23/10, 2008 at 17:19 Comment(8)
The only problem I have with this answer is that it accomplishes its task in a rather indirect and unclear fashion.Epagoge
For those whose want to look for the description of what the above means in the bash man page, look for the section "Parameter Expansion" and then for this text: "When not performing substring expansion, using the forms documented below, bash tests for a parameter that is unset or null ['null' meaning the empty string]. Omitting the colon results in a test only for a parameter that is unset (...) ${parameter:+word} : Use Alternate Value. If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted."Daloris
@Epagoge This is neither unclear nor indirect, but idiomatic. Perhaps to a programmer unfamiliar with ${+} and ${-} it is unclear, but familiarity with those constructs is essential if one is to be a competent user of the shell.Haze
See https://mcmap.net/q/12995/-how-to-tell-if-a-string-is-not-defined-in-a-bash-shell-script if you want to implement this with set -o nounset enabled.Ary
I did lots of testing on this; because results in my scripts were inconsistient. I suggest folk look into the "[ -v VAR ]" approach with bash-s v4.2 and beyond.Fiesta
what is xxx? can be anything?Hibbs
The xxx can be any non-empty string. It does have to be three characters long.Dissection
The xxx can be any non-empty string. It does not have to be three characters long (contradicting what was said in the prior comment). The accidental omission of 'not' makes that comment self-inconsistent.Dissection
T
44
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z works for undefined variables too. To distinguish between an undefined and a defined you'd use the things listed here or, with clearer explanations, here.

Cleanest way is using expansion like in these examples. To get all your options check the Parameter Expansion section of the manual.

Alternate word:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Default value:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

Of course you'd use one of these differently, putting the value you want instead of 'default value' and using the expansion directly, if appropriate.

Thimblerig answered 23/10, 2008 at 4:31 Comment(4)
But I want to distinguish between whether the string is "" or hasn't been defined ever. Is that possible?Inexact
this answer does tell how to distinguish between those two cases; follow the bash faq link it provides for more discussion.Valeric
I added that after his comment, CharlesThimblerig
Look up "Parameter Expansion" in the bash man page for all these "tricks". E.g. ${foo:-default} to use a default value, ${foo:=default} to assign the default value, ${foo:?error message} to display an error message if foo is unset, etc.Edithe
R
18

Advanced Bash scripting guide, 10.2. Parameter Substitution:

  • ${var+blahblah}: if var is defined, 'blahblah' is substituted for the expression, else null is substituted
  • ${var-blahblah}: if var is defined, it is itself substituted, else 'blahblah' is substituted
  • ${var?blahblah}: if var is defined, it is substituted, else the function exists with 'blahblah' as an error message.

To base your program logic on whether the variable $mystr is defined or not, you can do the following:

isdefined=0
${mystr+ export isdefined=1}

Now, if isdefined=0 then the variable was undefined, and if isdefined=1 the variable was defined.

This way of checking variables is better than the previous answers, because it is more elegant, readable, and if your Bash shell was configured to error on the use of undefined variables (set -u), the script will terminate prematurely.

Other useful stuff:

To have a default value of 7 assigned to $mystr if it was undefined, and leave it intact otherwise:

mystr=${mystr- 7}

To print an error message and exit the function if the variable is undefined:

: ${mystr? not defined}

Beware here that I used ':' so as not to have the contents of $mystr executed as a command in case it is defined.

Reiter answered 1/10, 2011 at 0:3 Comment(2)
I did not know about the ? syntax for BASH variables. That's a nice catch.Epagoge
This works also with indirect variable references. This passes: var= ; varname=var ; : ${!varname?not defined} and this terminates: varname=var ; : ${!varname?not defined}. But it is a good habit to use set -u which does the same much easier.Autochthon
B
18

A summary of tests.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"
Bindweed answered 9/6, 2012 at 23:23 Comment(3)
Good practice in bash. Sometimes file paths have spaces, or to protect against command injectionBindweed
I think with ${var+x} it is not necessary except if variable names are allowed to have spaces in them.Lobell
Not just good practice; it's required with [ to get correct results. If var is unset or empty, [ -n $var ] reduces to [ -n ] which has exit status 0, while [ -n "$var" ] has the expected (and correct) exit status of 1.Blackamoor
A
5

The explicit way to check for a variable being defined would be:

[ -v mystr ]
Apostate answered 5/6, 2013 at 18:6 Comment(4)
Can't find this in any of the man pages (for bash or test) but it does work for me.. Any idea what versions this was added?Kellby
It is documented in Conditional Expressions, referenced from the test operator in the tail end of the section Bourne Shell Builtins. I don't know when it was added, but it is not in the Apple version of bash (based on bash 3.2.51), so it is probably a 4.x feature.Dissection
This doesn't work for me with GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). The error is -bash: [: -v: unary operator expected. Per a comment here, it requires at minimum bash 4.2 to work.Prader
Thanks for checking when it became available. I had used it before on various systems, but then suddenly it didn't work. I came here out of curiosity and yes, of course the bash on this system is a 3.x bash.Gingerich
A
5

Test if a variable is set in bash when using "set -o nounset" contains a better answer (one that is more readable and works with set -o nounset enabled). It works roughly like this:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi
Ary answered 15/11, 2013 at 14:55 Comment(0)
U
1

Another option: the "list array indices" expansion:

$ unset foo
$ foo=
$ echo ${!foo[*]}
0

$ foo=bar
$ echo ${!foo[*]}
0

$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

The only time this expands to the empty string is when foo is unset, so you can check it with the string conditional:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1

$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0

$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0

$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

should be available in any Bash version 3.0 or greater.

Urethrectomy answered 27/3, 2012 at 22:23 Comment(0)
G
1

Not to shed this bike even further, but wanted to add

shopt -s -o nounset

is something you could add to the top of a script, which will error if variables aren't declared anywhere in the script.

The message you'd see is unbound variable, but as others mention, it won't catch an empty string or null value.

To make sure any individual value isn't empty, we can test a variable as it's expanded with ${mystr:?}, also known as dollar sign expansion, which would error with parameter null or not set.

Gadolinium answered 21/7, 2012 at 16:19 Comment(0)
M
1

The Bash Reference Manual is an authoritative source of information about Bash.

Here's an example of testing a variable to see if it exists:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(From section 6.3.2.)

Note that the whitespace after the open [ and before the ] is not optional.


Tips for Vim users

I had a script that had several declarations as follows:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

But I wanted them to defer to any existing values. So I rewrote them to look like this:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

I was able to automate this in Vim using a quick regex:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

This can be applied by selecting the relevant lines visually, then typing :. The command bar pre-populates with :'<,'>. Paste the above command and hit Enter.


It was tested on this version of Vim:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by [email protected]

Windows users may want different line endings.

Mithraism answered 1/12, 2015 at 19:49 Comment(0)
E
0

Here is what I think is a much clearer way to check if a variable is defined:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Use it as follows:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi
Epagoge answered 1/10, 2011 at 1:2 Comment(1)
Writing a function is not a bad idea; invoking grep is awful. Note that bash supports ${!var_name} as a way of getting the value of a variable whose name is specified in $var_name, so name=value; value=1; echo ${name} ${!name} yields value 1.Dissection
M
0

A shorter version to test an undefined variable can simply be:

test -z ${mystr} && echo "mystr is not defined"
Mitman answered 27/11, 2019 at 15:44 Comment(0)
A
-3

Call set without any arguments... it outputs all the defined variables.

The last ones on the list would be the ones defined in your script.

So you could pipe its output to something that could figure out what things are defined and what’s not.

Abbott answered 23/10, 2008 at 4:36 Comment(3)
I didn't down-vote this, but it is something of a sledgehammer to crack a rather small nut.Dissection
I actually like this answer better than the accepted answer. It's clear what is being done in this answer. The accepted answer is wonky as hell.Epagoge
It seems like this answer is more of a side-effect of the implementation of "set" than something that is desirable.Mosque

© 2022 - 2024 — McMap. All rights reserved.