How can I test if a variable is empty or contains only spaces?

553,471

Solution 1

First, note that the -z test is explicitly for:

the length of string is zero

That is, a string containing only spaces should not be true under -z, because it has a non-zero length.

What you want is to remove the spaces from the variable using the pattern replacement parameter expansion:

[[ -z "${param// }" ]]

This expands the param variable and replaces all matches of the pattern (a single space) with nothing, so a string that has only spaces in it will be expanded to an empty string.


The nitty-gritty of how that works is that ${var/pattern/string} replaces the first longest match of pattern with string. When pattern starts with / (as above) then it replaces all the matches. Because the replacement is empty, we can omit the final / and the string value:

${parameter/pattern/string}

The pattern is expanded to produce a pattern just as in filename expansion. Parameter is expanded and the longest match of pattern against its value is replaced with string. If pattern begins with ‘/’, all matches of pattern are replaced with string. Normally only the first match is replaced. ... If string is null, matches of pattern are deleted and the / following pattern may be omitted.

After all that, we end up with ${param// } to delete all spaces.

Note that though present in ksh (where it originated), zsh and bash, that syntax is not POSIX and should not be used in sh scripts.

Solution 2

The easy way to check that a string only contains characters in an authorized set is to test for the presence of unauthorized characters. Thus, instead of testing whether the string only contains spaces, test whether the string contains some character other than space. In bash, ksh or zsh:

if [[ $param = *[!\ ]* ]]; then
  echo "\$param contains characters other than space"
else
  echo "\$param consists of spaces only"
fi

“Consists of spaces only” includes the case of an empty (or unset) variable.

You may want to test for any whitespace character. Use [[ $param = *[^[:space:]]* ]] to use locale settings, or whatever explicit list of whitespace characters you want to test for, e.g. [[ $param = *[$' \t\n']* ]] to test for space, tab or newline.

Matching a string against a pattern with = inside [[ … ]] is a ksh extension (also present in bash and zsh). In any Bourne/POSIX-style, you can use the case construct to match a string against a pattern. Note that standard shell patterns use ! to negate a character set, rather than ^ like in most regular expression syntaxes.

case "$param" in
  *[!\ ]*) echo "\$param contains characters other than space";;
  *) echo "\$param consists of spaces only";;
esac

To test for whitespace characters, the $'…' syntax is specific to ksh/bash/zsh; you can insert these characters in your script literally (note that a newline will have to be within quotes, as backslash+newline expands to nothing), or generate them, e.g.

whitespace=$(printf '\n\t ')
case "$param" in
  *[!$whitespace]*) echo "\$param contains non-whitespace characters";;
  *) echo "\$param consists of whitespace only";;
esac

Solution 3

POSIXly:

case $var in
  (*[![:blank:]]*) echo '$var contains non blank';;
  (*) echo '$var contains only blanks or is empty or unset'
esac

To differentiate between blank, non-blank, empty, unset:

case ${var+x$var} in
  (x) echo empty;;
  ("") echo unset;;
  (x*[![:blank:]]*) echo non-blank;;
  (*) echo blank
esac

[:blank:] is for horizontal spacing characters (space and tab in ASCII, but there are probably a few more in your locale; some systems will include the non-breaking space (where available), some won't). If you want vertical spacing characters as well (like newline or form-feed), replace [:blank:] with [:space:].

Solution 4

The only remaining reason to write a shell script, instead of a script in a good scripting language, is if extreme portability is an overriding concern. The legacy /bin/sh is the only thing you can be certain you have, but Perl for instance is more likely to be available cross-platform than Bash. Therefore, never write shell scripts that use features that aren't truly universal -- and keep in mind that several proprietary Unix vendors froze their shell environment prior to POSIX.1-2001.

There is a portable way to make this test, but you have to use tr:

[ "x`printf '%s' "$var" | tr -d "$IFS"`" = x ]

(The default value of $IFS, conveniently, is a space, a tab, and a newline.)

(The printf builtin is itself spottily portable, but relying on it is much less hassle than figuring out which variant of echo you have.)

Solution 5

if [[ -n "${variable_name/[ ]*\n/}" ]]
then
    #execute if the the variable is not empty and contains non space characters
else
    #execute if the variable is empty or contains only spaces
fi
Share:
553,471

Related videos on Youtube

maihabunash
Author by

maihabunash

I am 17 years old and love to develop

Updated on September 18, 2022

Comments

  • maihabunash
    maihabunash almost 2 years

    The following bash syntax verifies if param isn't empty:

     [[ !  -z  $param  ]]
    

    For example:

    param=""
    [[ !  -z  $param  ]] && echo "I am not zero"
    

    No output and its fine.

    But when param is empty except for one (or more) space characters, then the case is different:

    param=" " # one space
    [[ !  -z  $param  ]] && echo "I am not zero"
    

    "I am not zero" is output.

    How can I change the test to consider variables that contain only space characters as empty?

    • dchirikov
      dchirikov almost 10 years
      From man test: -z STRING - the length of STRING is zero. If you want to remove all spaces in $param, use ${param// /}
    • Ciro Santilli Путлер Капут 六四事
      Ciro Santilli Путлер Капут 六四事 about 9 years
    • ADTC
      ADTC about 8 years
      WOW there is not a simple trim() function built-in to any *nix at all? So many different hacks to achieve something so simple...
    • Sameeksha
      Sameeksha about 8 years
      @ADTC $(sed 's/^\s+|\s+$//g' <<< $string) seems simple enough to me. Why reinvent the wheel?
    • Inversus
      Inversus almost 8 years
      Because it could be shinier
    • Noah Sussman
      Noah Sussman about 6 years
      Just noting that [[ ! -z $param ]] is equivalent to test ! -z $param.
    • papo
      papo over 3 years
      There is a hackinsh way, if you drop BASH '[[' it will actually work, but for the wrong reason. ! [ -z $param ] 2>/dev/null && echo no content '[' as a program will ignore multiple spaces between arguments and will fail if wrong number of arguments is provided with a result '1'. The same as if test -z was false. There is a special case of second argument '-o', but besides that, I would also argue against using it as even if its "working", it's not doing what it's intended to do.
    • papo
      papo over 3 years
      @Noah [ is equivalent to test. Sometimes it used to be a symlink. But [[ is BASH specific and there are many differences to program test. The reason of creating [[ was to make it easier to construct conditions. For that purpose it can't be the same as '[' or test.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 10 years
    That's a bit convoluted. The usual portable way to test whether a string contains certain characters is with case.
  • zwol
    zwol almost 10 years
    @Gilles I don't think it's possible to write a case glob to detect a string which is either empty, or contains only whitespace characters. (It would be easy with a regexp, but case doesn't do regexps. The construct in your answer won't work if you care about newlines.)
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 10 years
    I gave spaces as an example in my answer because that's what the question asked for. It's equally easy to test for whitespace characters: case $param in *[![:space:]]*) …. If you want to test for IFS characters, you can use the pattern *[$IFS]*.
  • mikeserv
    mikeserv almost 10 years
    @Gilles - that is true, and mostly well-put, but your answer still fails entirely to determine certainly whether a variable is either empty or that it consists only of space characters because, as is, it currently cannot reliably distinguish between an empty and an unset shell variable.
  • mikeserv
    mikeserv almost 10 years
    This is almost excellent - but, as yet, it does not answer the question as asked. It currently can tell you the difference between an unset or empty shell variable and one which contains only spaces. If you will accept my suggestion you will add the param expansion form not yet convered by any other answer that explicitly tests a shell variable for being set - I mean ${var?not set} - passing that test and surviving would ensure that a variable matched by your *) case would effect a definitive answer, for example.
  • zwol
    zwol almost 10 years
    @Gilles [[:charclass:]] is not a feature you can use under the portability constraints I described above (i.e. "truly universal"). I don't know, but I very strongly suspect *[$IFS]* is also unreliable.
  • mikeserv
    mikeserv almost 10 years
    Correction - this, in fact, would answer the question originally asked, but it has since been edited in such a way that it fundamentally changes the meaning of the question and therefore invalidates your answer.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 10 years
    @Zack [[:charclass:]] is POSIX, but not supported by , and risky because it's locale-dependent. *[$IFS]* would work in any shell, why do you think it would be unreliable?
  • Bernhard
    Bernhard almost 10 years
    I think your answer is downvoted because the "or contain spaces" is not true.
  • mikeserv
    mikeserv almost 10 years
    be careful - that kills the current shell. Better to put it in a (: ${subshell?}). Also - that will write to stderr - you probably want redirect. And most important that evaluates to the variable's actual value if it is not unset or null. If there's a command in there, you just ran it. It's best to run that test on :.
  • mikeserv
    mikeserv almost 10 years
    @Gilles - *[$IFS]* does have a problem if it is set to a value other than that for which you mean to match. And that's the same problem this answer has - it's just a variable like any other. Strange that it should go to such great lengths to achieve two decades worth of portability, and then rely on a variable's value which it does not set explicitly. In what locale does [[:space:]] not evaluate to <space> <tab> and <newline>?
  • zwol
    zwol almost 10 years
    @Gilles I know it's not supported by older Solaris /bin/sh, at least (what did you have in mind?) I don't know either way, but I wouldn't trust shells that old to do variable expansion at all inside case globs.
  • zwol
    zwol almost 10 years
    @mikeserv In a really seriously defensive script, you explicitly set IFS to <space><tab><newline> at the beginning and then never touch it again. I thought that'd be a distraction.
  • mikeserv
    mikeserv almost 10 years
    You should use it to split as needed, but I agree that it is better to be explicit.
  • pratik
    pratik almost 10 years
    how it will kill shell. and u can also test this , first define name=aaaa then run this command echo ${name:?variable is empty} it will print value of variable name and now run this command echo ${name1:?variable is empty} it will print -sh: name1: variable is empty
  • mikeserv
    mikeserv almost 10 years
    Yes - echo ${name:?variable is empty} will either evaluate to $name's non-null value or it will kill the shell. So for the same reason you don't do prompt > $randomvar neither should you ${var?} - if there's a command in there, it's probably going to run - and you don't know what it is. An interactive shell doesn't have to exit - which is why yours doesn't. Still, if you want to see some tests, there are a lot of them here.. This one isn't quite as long...
  • Stéphane Chazelas
    Stéphane Chazelas almost 10 years
    You need [[ $param = *[!\ ]* ]] in mksh.
  • Ken Sharp
    Ken Sharp over 8 years
    POSIXly is ideal because it will work with the (arguably better) dash.
  • cuonglm
    cuonglm over 8 years
    zsh seems to have problem with [![:blank:]], it raise :b is invalid modifier. In sh and ksh emulate, it raise event not found for [
  • Stéphane Chazelas
    Stéphane Chazelas over 8 years
    @cuonglm, what did you try? var=foo zsh -c 'case ${var+x$var} in (x*[![:blank:]]*) echo x; esac' is OK for me. The event not found would only be for interactive shells.
  • cuonglm
    cuonglm over 8 years
    @StéphaneChazelas: Ah, right, I tried with interactive shells. The :b invalid modifier is a bit strange.
  • cuonglm
    cuonglm over 8 years
    @StéphaneChazelas: Anyway, I expect it works in emulate sh and ksh, whether interactive or not. Any ideal?
  • Stéphane Chazelas
    Stéphane Chazelas over 8 years
    @cuonglm, I can't reproduce the :b thing. What version of zsh, what command line?
  • cuonglm
    cuonglm over 8 years
    @StéphaneChazelas: zsh version 5.2, command case $1 in ([![:blank:]]) echo 1;; esac
  • Franklin Yu
    Franklin Yu almost 8 years
    On my machine [:blank:] only match a single space, not a tab. I read that it is locale specific?
  • Stéphane Chazelas
    Stéphane Chazelas almost 8 years
    @FranklinYu, that would be possible but unheard of. What system is it? What locale? Does bash -c "case $'\t' in [[:blank:]]) echo yes; esac" really not output yes?
  • Franklin Yu
    Franklin Yu almost 8 years
    @StéphaneChazelas My bad. I thought '\t' suffices, because I tested it with echo. However on OS X, echo by default interpretates the ANSI-C escapes, for both Zsh and Bourne shell (but not Bash, so maybe the Bourne shell is faked by Zsh) in interactive mode. Now my issue becomes [:space:] not including $'\n' or $'\v'.
  • Stéphane Chazelas
    Stéphane Chazelas almost 8 years
    @FranklinYu, on OS/X sh is bash built with --enable-xpg-echo-default and --enable-strict-posix-default so as to be Unix compliant (which involves echo '\t' outputting a tab).
  • Alexander Mills
    Alexander Mills over 7 years
    this gets rid of any white space character, not just a single space, right? thanks
  • mikezter
    mikezter about 7 years
    use sed they said... just echo the variable they said...
  • Jesse Chisholm
    Jesse Chisholm about 7 years
    Hmm. If it is ${var/pattern/string} then shouldn't it be [[ -z ${param/ /} ]] with the space between the slashes to be the pattern and nothing after to be the string?
  • Michael Homer
    Michael Homer about 7 years
    @JesseChisholm "Because the replacement is empty, we can omit the final / and the string value"; "If string is null, matches of pattern are deleted and the / following pattern may be omitted."
  • Jesse Chisholm
    Jesse Chisholm about 7 years
    @MichaelHomer The answer [[ -z "${param// }" ]] sure looks like the pattern is empty and the replacement string is a single space. AH! I see it now if pattern begins with a slash I missed that part. so the pattern is / and the string is left off and therefore empty. Thanks.
  • zwol
    zwol over 6 years
    @Jean-LucNacifCoelho Please just try using a different scripting language for everything that you would normally write shell scripts for, for a couple months. I bet you will come around to my point of view.
  • Brian Chrisman
    Brian Chrisman over 6 years
    bash note: if running in set -o nounset (set -u), and param is unset (rather than null or space-filled), then this will generate an unbound variable error. ( set +u; ..... ) would exempt this test.
  • Noam Manos
    Noam Manos about 4 years
    This approach will not catch multi-line variable with spaces. Try xargs instead: [[ -z $(echo $var | xargs) ]]
  • Lee Meador
    Lee Meador almost 3 years
    Yes ${myVar// } works. Also ${myVar%%*( )} and ${myVar##*( )} and ${myVar##+( )} and its because if we remove leading, trailing or all spaces in a string it will become empty if it only contains spaces.
  • ilkkachu
    ilkkachu over 2 years
    awk would also consider tabs as "empty", not just spaces. Also see: When is double-quoting necessary?
  • Jeff Schaller
    Jeff Schaller over 2 years
  • Stéphane Chazelas
    Stéphane Chazelas over 2 years
    There are a lot of non-blank values of $var for which [[ -z $(echo $var | xargs) ]] would return true, like -n, -e \x20, "" '' \ \ \ "", etc, not to mention all the issues related to globbing.
  • Stéphane Chazelas
    Stéphane Chazelas over 2 years
    You're confusing $"..." with $'...'
  • Stéphane Chazelas
    Stéphane Chazelas over 2 years
    That would also not work properly for variables containing newline characters.
  • Stéphane Chazelas
    Stéphane Chazelas over 2 years
    Note that bash uses the system's regexp API to do regexp matching. \s is not a standard regexp operator, so that won't work on all systems. The standard equivalent is [[:space:]] (and [[:blank:]] for the \h equivalent).