18

What is the portable and canonical way to test if variable is empty/undefined in a shell script? It should work in all sh-like shells. What I do now is something like:

if [ -z "$var" ] ; then
    ...

and for reverse, doing something when variable is not empty/undefined:

if [ -n "$var" ] ; then
    ...

And while these work for the scripts I write now, I'd like to know a way, that will work in any reasonably compatible sh-like shell, even on some more obscure environment than a Linux PC with GNU userland and bash or dash.

I'm looking for an "expert answer" from someone who is experienced in different shell environments and knows the pitfalls of different ways of doing things, not an opinion like "that should work".

hyde
  • 60,639
  • 21
  • 115
  • 176
  • 1
    Your way *is* the way. The examples you have both follow the POSIX standard, and are accepted by `dash` (as well as `bash` and `zsh`). As long as you remember to always quote your variables (like you have done) *you are absolutely safe*. Except possibly for some *really old* shells. For details about those weird cases, see my answer below. – zrajm Jul 23 '14 at 22:32
  • @zrajim - this way does not test a variable - it tests a variable's expansion. – mikeserv Jul 24 '14 at 15:39
  • You're right, it test the variable's expansion. And if the variable is empty/undefined it, will expand to the empty string so we'll only have to test for that to see whether its empty or undefined. – zrajm Jul 26 '14 at 13:25

5 Answers5

15

The bracket construction [...] is part of the POSIX standard, and safe to just about everywhere. So, to test if a variable is empty the if case used in the question is perfect:

if [ -z "$STRING" ]; then
    # $STRING is empty
fi

As long as you remember than:

Always quote the variables

A non-quoted variable is subject to word splitting, so that if you use [ -z $STRING ] (without quotes around $STRING) and $STRING contains spaces, or is empty, the shell will see [ -z SEVERAL WORDS ] or [ -z ] which are both syntax errors – i.e. not what you want.

Always use just one equal sign

An alternative way if testing if a string in a POSIX shell is empty is to use =.

if [ "$STRING" = "" ]; then
    # $STRING is empty
fi

Note however that you should never use double equal signs (==) inside [...]! (This only works is bash, or when sh is a link to bash – but if run on a different machine where sh links to something else, it will not work.) If you want to use the double equal sign, you should also have double brackets [[...]] (which only works in zsh, bash etc., but not plain POSIX shells like dash).

Don't use && or || inside [...]

It might work in some shells, but it sure won't work in all of them. Instead, for 'and' write [...] && [...] or [... -a ...]. And for 'or' write [...] || [...] or [... -o ...].

Ancient stuff

Sometimes, in really ancient shell scripts you'll see stuff like the following, to test for an empty variable. This is still perfectly valid (but looks kind of clunky in my eyes) and I've never encountered a shell that requires this (though, admittedly, my experience is limited outside Linux – maybe there are BSD shells that still need this?)

if [ ".$STRING" = "." ]; then
    # $STRING is empty
fi

The prepending of a period is to avoid the bracket construction [...] from being confused if $STRING happen to contain -e, -o and other options that [...] uses. By prepending a period, it is turned into a non-option.

I have never seen a shell need this workaround, though. (Dash does certainly not need them.) So it is presumably safe to ignore this.

zrajm
  • 1,361
  • 1
  • 12
  • 21
  • ummm... but how do you tell if it is empty? neither form of `[ test ]` can portably tell you if a variable is empty - they can only portably tell you what its expansion evaluates to. `empty != unset` – mikeserv Jul 24 '14 at 11:22
  • D'oh! You're absolutely right! I've rephrased my answer and several included examples of how to do what the question asked for. – zrajm Jul 26 '14 at 13:10
  • almost... The thing is - `$STRING` isn't certainly *empty* - it may be *either* unset *or* empty. Unless you define conditional expansions, you cannot tell one way or the other - not without `set -u` and an unquoted expansion, that is. Like: `[ -z "${STRING+1}" ]` will tell you if `$STRING` is *unset* and following that with `[ -z "${STRING:+1}" ]` will tell you if it is *empty*. Or maybe `[ -n "${STRING+1}" ] && [ ${#STRING} -eq 0 ]` or something. This can make a *big* difference when interacting with empty readonlies for instance. – mikeserv Jul 26 '14 at 14:49
  • What about existence? – Royi Dec 27 '20 at 01:40
7

Disclaimer: I don't consider myself an expert in shells.

AFAIK, the test command and the -z and -n flags are part of the POSIX standard, so you should be fine by using them across many platforms.

rvidal
  • 2,012
  • 1
  • 17
  • 24
  • 3
    They are: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html – chepner Nov 05 '13 at 15:03
  • 1
    **Test brackets `[...]` are ALSO part of the POSIX standard and far more readable – stick to them.** Note, however, that the doubled brackets `[[...]]` should **not** be used when you're striving for compatibility – that construction is specific to more complex shells (`zsh`, `bash` etc.). – zrajm Jul 23 '14 at 00:13
  • 2
    Anything **working with POSIX shell** is **-not-** exactly going to work with ***any reasonably compatible sh-like shell***. POSIX is a **newcomer** and was not created before the original `sh`. People should know their standards before they try promoting it. – konsolebox Jul 27 '14 at 05:48
  • "Newcomer" might be a little much for something introduced in 1992. POSIX sh is well past voting age, even if its pappy Bourne has a few decades on it. – Charles Duffy Sep 01 '20 at 14:42
  • I personally prefer `test -n` syntax over square brackets. – Sridhar Sarnobat Nov 07 '22 at 06:54
4

It's important to remember that an empty shell variable is not the same thing as an unset shell variable - though this distinction cannot be made using either form of [ test ]. All that test can do is report on the value of a variable's expansion - it is parameter expansion that determines that. For example:

var=value
echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
###OUTPUT###
I am $var and I am set. I am currently not empty.

Again:

var=
echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
###OUTPUT###
I am $var and I am set. I am currently empty.

With [ test ] :

{   _nstr() { echo 'I just evaluated a ""null string!' ; }
    [ -z "$var" ] && _nstr
    [ -z "" ] && _nstr
    unset var    
    [ -z "$var" ] && _nstr
    echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
}
###OUTPUT###
I just evaluated a ""null string!
I just evaluated a ""null string!
I just evaluated a ""null string!

Hopefully this drives it home:

var=value
[ -z "${var+}" ] && _nstr
###OUTPUT###    
I just evaluated a ""null string!

If you want to portably determine if a variable is empty this will do it:

${var:+false} [ -n "${var+1}" ] && echo \$var is set and null

I think you could make very good use of the shell's parameter expansion for cases like these and in some cases even kill two birds with one stone (see the first example). This should definitely be portable. What follows is an incomplete copy/paste from the open group's POSIX shell guidelines found here: http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02

${parameter:-word}

  • Use Default Values. If parameter is unset or null, the expansion of word shall be substituted; otherwise, the value of parameter shall be substituted.

${parameter:=word}

  • Assign Default Values. If parameter is unset or null, the expansion of word shall be assigned to parameter. In all cases, the final value of parameter shall be substituted. Only variables, not positional parameters or special parameters, can be assigned in this way.

${parameter:?[word]}

  • Indicate Error if Null or Unset. If parameter is unset or null, the expansion of word (or a message indicating it is unset if word is omitted) shall be written to standard error and the shell exits with a non-zero exit status. Otherwise, the value of parameter shall be substituted. An interactive shell need not exit.

${parameter:+word}

  • Use Alternative Value. If parameter is unset or null, null shall be substituted; otherwise, the expansion of word shall be substituted.

EXAMPLES:

${parameter:-word}

  • In this example, ls is executed only if x is null or unset. (The $( ls) command substitution notation is explained in Command Substitution.) ${x:-$(ls)}

${parameter:=word}

% unset X
% echo ${X:=abc}
abc

EDIT:

I just noticed Ashish's one-liner, and because it felt kinda cheap just pasting that in, I'd like to do something. So here's a POSIX parameter expansion set-test one-liner:

_JAIL="" ; echo "_JAIL is ${_JAIL:-unset or null.}"

Well, still cheap, I guess.

EDIT2:

So I just noticed this: "...while these work for the scripts I write now, I'd like to know a way, that will work in any reasonably compatible sh-like shell, even on some more obscure environment than a Linux PC with GNU userland and bash or dash..."

I am sorry I'm not personally an expert, but I first picked up the following method of defining important shell variables from arguments that may or may not have been passed after digging through some fairly expertly written initramfs busybox scripts. It will work as expected pretty much everywhere.

It's kinda beautiful because you only set the variables to the default condition if the user doesn't define them without any extra work to check if they did, really. There are tons of other uses of course, I'm just not smart enough to use them like I should.

_VAR1=${1:-/default/path} ; _VAR2=${2:-"$_default_size"}

EDIT3:

phs was right to specify that his method tests for empty variables not absent ones as was posed in the question. This method can do the same depending on whether the center colon is used. I've copied from the POSIX guidelines below a sort of a crap table to demonstrate this.

{subs='substitute'; params='parameters'; assn='assign'; err='error'} 

         "$parameter" == |set && !null |set && null|  !set
       ------------------ ------------- ----------- ----------
      ${parameter:-word} | subs params | subs word | subs word
       ------------------ ------------- ----------- ----------
      ${parameter-word}  | subs params | subs null | subs word
       ------------------ ------------- ----------- ----------
      ${parameter:=word} | subs params | assn word | assn word
       ------------------ ------------- ----------- ----------
      ${parameter=word}  | subs params | subs null | assn word
       ------------------ ------------- ----------- ----------
      ${parameter:?word} | subs params | err; exit | err; exit
       ------------------ ------------- ----------- ----------
      ${parameter?word}  | subs params | subs null | err; exit
       ------------------ ------------- ----------- ----------
      ${parameter:+word} |  subs word  | subs null | subs null
       ------------------ ------------- ----------- ----------
      ${parameter+word}  |  subs word  | subs word | subs null
mikeserv
  • 694
  • 7
  • 9
  • Much nice information about parameter expansion, you didn't even try to answer the actual question, did you? – zrajm Jul 23 '14 at 22:38
  • 1
    @zrajm - you might have a point. This was one of my first ever answers - I should read it again. But `${v:+:} echo if you see this - its empty` – mikeserv Jul 23 '14 at 23:59
  • @zrajim - I take that back - you don't have a point. immediately above your comment is a formatted table with the headings `set && !null`, `set && null` and `!set` that describes in detail the behavior of each type of expansion based on the contents of the variable - including an entire column for *empty*. Would you care to take another look? – mikeserv Jul 24 '14 at 11:18
  • Yes. You're right. There is an answer in there. I retract my downvote. – zrajm Jul 26 '14 at 13:27
1

Portable and canonical are two different things.

For portable, the test I see in several autoconf scripts is:

if test "x$MY_VAR" = x; then
  # ...
fi

This tests for emptiness, not absence.

phs
  • 10,687
  • 4
  • 58
  • 84
0

You don't have to go in if else ladder unless you have big codes running in testing variables

one linner example

_JAIL=""
[  -z "$_JAIL" ] && echo "No" || echo "Yes"

Click here For more examples.

Ashish
  • 1,856
  • 18
  • 30
  • The question was about portable way to do the test itself. Your code is essentially as portable or unportable, as code in question. – hyde Nov 05 '13 at 11:48