How to tell if a string is not defined in a Bash shell script
Solution 1
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
).
Solution 2
~> 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.
Solution 3
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.
Solution 4
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"
Solution 5
The explicit way to check for a variable being defined would be:
[ -v mystr ]
Related videos on Youtube
Erik
Updated on August 25, 2021Comments
-
Erik over 2 years
I have two servers each running a modified version of ZF2 Skeleton Application.
Server1:8080 - REST API that just returns JSON
Server2:8081 - Web client serving our customer
I am getting the famed 'White Screen of Death' in Server 2.
Visit a user's webpage via the route /%s - %s being their username, everything looks fine
User is presented with a status text box and submit button
I click submit with some text, and get the White Screen
I recently added an ability to POST a status update to the page.
server2:80 127.0.0.1 - - [03/Apr/2014:21:52:27 -0700] "POST /myusername HTTP/1.1" 500 357 "server1:8081/hwy9nightkid" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/33.0.1750.152 Chrome/33.0.1750.152 Safari/537.36"
Notice the log file is citing port 80.. not sure why.. but just to double check I looked at my endpoint.. it's working fine for everything else (user's profile data displays)
zf2-server2/module/Api/src/Api/Client/ApiClient.php: protected static $endpointHost = 'http://server2.local:8080';
This site was working great until I added this module's ability to POST data to Server2.. any ideas on how to debug this the Zend way? I'm new so I may just start littering the system with logging information to trace where it's truly stuck, maybe grab an IDE like PHPStorm if that would help.
I know for a fact the POST is received by the Server 1.. my guess is something happened processing that data.. and a 500 error was returned instead, but shouldn't that give me a nice looking error page???
Update & progress with 500 Error
In order to debug, I used an application called Postman
www.getpostman.com
Thanks to this utility for Chrome, I create a simple POST to my service and found an error message was being returned wrapped in JSON.
Error 500 : Table 'tablename' doesn't exist in db.
I'm going to look up how to properly handle errors / json responses that are unexpected.. and how to properly re-route my client to an error page.
Update TL;DR - Seems my Server2 Web Client is failing to either Log this error AND OR display a 500 error page.
-
Charles Duffy over 15 yearsplease be in the habit of using [ -z "$mystr" ] as opposed to [ -z $mystr ]
-
Open the way almost 13 yearshow to do the inverse thing? I mean when the string is not null
-
Lekensteyn over 12 years@flow: What about
[ -n "${VAR+x}"] && echo not null
-
ffledgling about 11 years@CharlesDuffy I've read this before in a lot of online resources. Why is this preferred ?
-
Charles Duffy about 11 years@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.
-
Setjmp over 15 yearsBut I want to distinguish between whether the string is "" or hasn't been defined ever. Is that possible?
-
Charles Duffy over 15 yearsthis answer does tell how to distinguish between those two cases; follow the bash faq link it provides for more discussion.
-
Vinko Vrsalovic over 15 yearsI added that after his comment, Charles
-
Jouni K. Seppänen over 15 yearsLook 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.
-
Jonathan Leffler over 15 yearsI didn't down-vote this, but it is something of a sledgehammer to crack a rather small nut.
-
Swiss over 12 yearsI 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.
-
Swiss over 12 yearsThe only problem I have with this answer is that it accomplishes its task in a rather indirect and unclear fashion.
-
Swiss over 12 yearsI did not know about the ? syntax for BASH variables. That's a nice catch.
-
Jonathan Morgan over 12 yearsIt seems like this answer is more of a side-effect of the implementation of "set" than something that is desirable.
-
David Tonhofer about 12 yearsFor 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."
-
William Pursell over 11 years@Swiss 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. -
ceving about 11 yearsThis 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 useset -u
which does the same much easier. -
Ash Berlin-Taylor over 10 yearsCan'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?
-
Gili over 10 yearsSee stackoverflow.com/a/20003892/14731 if you want to implement this with
set -o nounset
enabled. -
Jonathan Leffler over 10 yearsWriting a function is not a bad idea; invoking
grep
is awful. Note thatbash
supports${!var_name}
as a way of getting the value of a variable whose name is specified in$var_name
, soname=value; value=1; echo ${name} ${!name}
yieldsvalue 1
. -
Jonathan Leffler over 10 yearsIt 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 ofbash
(based onbash
3.2.51), so it is probably a 4.x feature. -
Asclepius about 10 yearsThis 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. -
k107 about 9 yearsGood practice in bash. Sometimes file paths have spaces, or to protect against command injection
-
Anne van Rossum about 9 yearsI think with
${var+x}
it is not necessary except if variable names are allowed to have spaces in them. -
clacke over 8 yearsThanks 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.
-
will about 8 yearsI 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. -
chepner about 8 yearsNot just good practice; it's required with
[
to get correct results. Ifvar
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. -
ToiletGuy about 3 yearswhat is xxx? can be anything?
-
Jonathan Leffler about 3 yearsThe
xxx
can be any non-empty string. It does have to be three characters long. -
Jonathan Leffler over 2 yearsThe
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.