260

I've seen Bash scripts test for a non-zero length string in two different ways. Most scripts use the -n option:

#!/bin/bash
# With the -n option
if [ -n "$var" ]; then
  # Do something when var is non-zero length
fi

But the -n option isn't really needed:

# Without the -n option
if [ "$var" ]; then
  # Do something when var is non-zero length
fi

Which is the better way?

Similarly, which is the better way for testing for zero-length:

if [ -z "$var" ]; then
  # Do something when var is zero-length
fi

or

if [ ! "$var" ]; then
  # Do something when var is zero-length
fi

7 Answers 7

532
+100

Edit: This is a more complete version that shows more differences between [ (aka test) and [[.

The following table shows that whether a variable is quoted or not, whether you use single or double brackets and whether the variable contains only a space are the things that affect whether using a test with or without -n/-z is suitable for checking a variable.

     | 1a    2a    3a    4a    5a    6a   | 1b    2b    3b    4b    5b    6b
     | [     ["    [-n   [-n"  [-z   [-z" | [[    [["   [[-n  [[-n" [[-z  [[-z"
-----+------------------------------------+------------------------------------
unset| false false true  false true  true | false false false false true  true
null | false false true  false true  true | false false false false true  true
space| false true  true  true  true  false| true  true  true  true  false false
zero | true  true  true  true  false false| true  true  true  true  false false
digit| true  true  true  true  false false| true  true  true  true  false false
char | true  true  true  true  false false| true  true  true  true  false false
hyphn| true  true  true  true  false false| true  true  true  true  false false
two  | -err- true  -err- true  -err- false| true  true  true  true  false false
part | -err- true  -err- true  -err- false| true  true  true  true  false false
Tstr | true  true  -err- true  -err- false| true  true  true  true  false false
Fsym | false true  -err- true  -err- false| true  true  true  true  false false
T=   | true  true  -err- true  -err- false| true  true  true  true  false false
F=   | false true  -err- true  -err- false| true  true  true  true  false false
T!=  | true  true  -err- true  -err- false| true  true  true  true  false false
F!=  | false true  -err- true  -err- false| true  true  true  true  false false
Teq  | true  true  -err- true  -err- false| true  true  true  true  false false
Feq  | false true  -err- true  -err- false| true  true  true  true  false false
Tne  | true  true  -err- true  -err- false| true  true  true  true  false false
Fne  | false true  -err- true  -err- false| true  true  true  true  false false

If you want to know if a variable is non-zero length, do any of the following:

  • quote the variable in single brackets (column 2a)
  • use -n and quote the variable in single brackets (column 4a)
  • use double brackets with or without quoting and with or without -n (columns 1b - 4b)

Notice in column 1a starting at the row labeled "two" that the result indicates that [ is evaluating the contents of the variable as if they were part of the conditional expression (the result matches the assertion implied by the "T" or "F" in the description column). When [[ is used (column 1b), the variable content is seen as a string and not evaluated.

The errors in columns 3a and 5a are caused by the fact that the variable value includes a space and the variable is unquoted. Again, as shown in columns 3b and 5b, [[ evaluates the variable's contents as a string.

Correspondingly, for tests for zero-length strings, columns 6a, 5b and 6b show the correct ways to do that. Also note that any of these tests can be negated if negating shows a clearer intent than using the opposite operation. For example: if ! [[ -n $var ]].

If you're using [, the key to making sure that you don't get unexpected results is quoting the variable. Using [[, it doesn't matter.

The error messages, which are being suppressed, are "unary operator expected" or "binary operator expected".

This is the script that produced the table above.

#!/bin/bash
# by Dennis Williamson
# 2010-10-06, revised 2010-11-10
# for http://stackoverflow.com/q/3869072
# designed to fit an 80 character terminal

dw=5    # description column width
w=6     # table column width

t () { printf '%-*s' "$w" " true"; }
f () { [[ $? == 1 ]] && printf '%-*s' "$w" " false" || printf '%-*s' "$w" " -err-"; }

o=/dev/null

echo '     | 1a    2a    3a    4a    5a    6a   | 1b    2b    3b    4b    5b    6b'
echo '     | [     ["    [-n   [-n"  [-z   [-z" | [[    [["   [[-n  [[-n" [[-z  [[-z"'
echo '-----+------------------------------------+------------------------------------'

while read -r d t
do
    printf '%-*s|' "$dw" "$d"

    case $d in
        unset) unset t  ;;
        space) t=' '    ;;
    esac

    [ $t ]        2>$o  && t || f
    [ "$t" ]            && t || f
    [ -n $t ]     2>$o  && t || f
    [ -n "$t" ]         && t || f
    [ -z $t ]     2>$o  && t || f
    [ -z "$t" ]         && t || f
    echo -n "|"
    [[ $t ]]            && t || f
    [[ "$t" ]]          && t || f
    [[ -n $t ]]         && t || f
    [[ -n "$t" ]]       && t || f
    [[ -z $t ]]         && t || f
    [[ -z "$t" ]]       && t || f
    echo

done <<'EOF'
unset
null
space
zero    0
digit   1
char    c
hyphn   -z
two     a b
part    a -a
Tstr    -n a
Fsym    -h .
T=      1 = 1
F=      1 = 2
T!=     1 != 2
F!=     1 != 1
Teq     1 -eq 1
Feq     1 -eq 2
Tne     1 -ne 2
Fne     1 -ne 1
EOF
4
  • 1
    Thanks! I've decided to adopt the style of "quote the variable in single brackets (column 2a)" IMO, the -n just adds noise and decreases readability. Similarly, for testing for zero-length or unset, I'll use [ ! "$var" ] instead of [ -z "$var" ]. Commented Oct 6, 2010 at 20:32
  • 3
    So your chart for [" vs [-n" (the OP's first question) shows that they are completely equivalent, right?
    – hobs
    Commented Jul 3, 2014 at 18:32
  • 1
    @hobs: Yes, and which to use depends on which is more clear. You'll also notice that their equivalent when using the double-bracket form which is preferred when using Bash. Commented Jul 3, 2014 at 18:36
  • 2
    So, to summarize your awesome table, the clearer ("better") way to test for non-null strings is [["
    – hobs
    Commented Jul 3, 2014 at 18:57
60

It is better to use the more powerful [[ as far as Bash is concerned.

Usual cases

if [[ $var ]]; then   # var is set and it is not empty
if [[ ! $var ]]; then # var is not set or it is set to an empty string

The above two constructs look clean and readable. They should suffice in most cases.

Note that we don't need to quote the variable expansions inside [[ as there is no danger of word splitting and globbing.

To prevent shellcheck's soft complaints about [[ $var ]] and [[ ! $var ]], we could use the -n option.

Rare cases

In the rare case of us having to make a distinction between "being set to an empty string" vs "not being set at all", we could use these:

if [[ ${var+x} ]]; then           # var is set but it could be empty
if [[ ! ${var+x} ]]; then         # var is not set
if [[ ${var+x} && ! $var ]]; then # var is set and is empty

We can also use the -v test:

if [[ -v var ]]; then             # var is set but it could be empty
if [[ ! -v var ]]; then           # var is not set
if [[ -v var && ! $var ]]; then   # var is set and is empty
if [[ -v var && -z $var ]]; then  # var is set and is empty

Related posts and documentation

There are a plenty of posts related to this topic. Here are a few:

1
  • I don't think it is necessarily better to use [[. It's a matter of personal preference. Even the link you reference recommends always using []. Commented Jan 28, 2022 at 19:53
17

Here are some more tests

True if string is not empty:

[ -n "$var" ]
[[ -n $var ]]
test -n "$var"
[ "$var" ]
[[ $var ]]
(( ${#var} ))
let ${#var}
test "$var"

True if string is empty:

[ -z "$var" ]
[[ -z $var ]]
test -z "$var"
! [ "$var" ]
! [[ $var ]]
! (( ${#var} ))
! let ${#var}
! test "$var"
1
  • This doesn't answers OP's question of what is the better way. Commented Apr 13, 2018 at 22:20
3

Unfortunately, I haven't been able to find out why there are two syntaxes for this in the first place. Both seem to have been introduced at the same time with Version 7 Unix in 1979.

The 1994 Single Unix Specification preferred the -n and -z syntax. The same recommendation has been copied verbatim into every version of POSIX since, including the latest version POSIX.1-2017:

The two commands:

test "$1"
test ! "$1"

could not be used reliably on some historical systems. Unexpected results would occur if such a string expression were used and $1 expanded to '!', '(', or a known unary primary. Better constructs are:

test -n "$1"
test -z "$1"

respectively.

I suppose that if you want to pedantically follow every recommendation in the specification then you should keep using -n and -z, but seriously, do you need compatibility with systems that are over 28 years old?

In my opinion, the readability of if [ "$var" ] and if [ ! "$var" ] makes that syntax the clear winner. But the bottom line is that there's no functional difference, so go with your personal preference.

0

The correct answer is the following:

if [[ -n $var ]] ; then
  blah
fi

Note the use of the [[...]], which correctly handles quoting the variables for you.

2
  • 2
    Why use -n when it is not really needed in Bash? Commented Apr 15, 2018 at 5:08
  • @codeforester BASH (Bourne Again Shell) is the progeny of Bourne Shell, so it inherited the -n and -z test operators but then added the ! negation on top of it. Moreover, this is conceptually the same as the difference between == and != in higher level languages which provide affirmative and negative operators simply to expand the range of possible semantics. Sometimes it is much easier to assemble an expression that doesn't need to rely on double negatives, and so on.
    – ingyhere
    Commented Sep 27, 2021 at 15:33
0

An alternative and perhaps more transparent way of evaluating an empty environment variable is to use...

  if [ "x$ENV_VARIABLE" != "x" ] ; then
      echo 'ENV_VARIABLE contains something'
  fi
3
  • 12
    This is very old-school from the Bourne shell days. Don't perpetuate this old habit. bash is a sharper tool than its predecessors.
    – tbc0
    Commented Jul 26, 2016 at 22:00
  • 4
    You don't even need this with pre-bash shells, if you're avoiding syntax that the POSIX standard explicitly marks obsolescent. [ "$ENV_VARIABLE" != "" ] will work on every shell with a POSIX-compliant test implementation -- not just bash, but ash/dash/ksh/etc. Commented Aug 15, 2018 at 17:37
  • 2
    ...which is to say, don't use -a or -o to combine tests but instead use [ ... ] && [ ... ] or [ ... ] || [ ... ] and the only corner cases that can apply to using arbitrary variables' data in a test are unambiguously closed. Commented Aug 15, 2018 at 17:40
-6

Use case/esac to test:

case "$var" in
  "") echo "zero length";;
esac
3
  • 17
    No, please. This isn't what case is meant for.
    – tbc0
    Commented Jul 26, 2016 at 21:59
  • 2
    case works best when there are more than two alternatives. Commented Apr 13, 2018 at 22:19
  • case is not intended for this kind of use. Commented May 27, 2019 at 21:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.