2

In bash I can use -z string and -n string to see if the string length is zero or not. Would like to write a bash function that returns true or false, not only if length is zero but also if it is all whitespace. Or is there a way without needing a function?

Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47
Angio
  • 57
  • 1
  • 5

3 Answers3

2

You can use a regular expression:

if [[ $string =~ ^" "*$ ]]

or you can remove all the spaces before testing with -z:

if [[ -z "${string// /}" ]]
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Both are different as `[:space:]` encompasses a larger set of possible characters. – kvantour Sep 08 '21 at 20:40
  • @kvantour If you want to include all spacey characters in the second form, use `[[ -z "${string//[[:space:]]/}" ]]` – Gordon Davisson Sep 08 '21 at 20:43
  • I know, I just wasn't sure how to just match space in a bash regexp, because quoting a regexp makes it literal. @kvantour – Barmar Sep 08 '21 at 20:44
  • @Barmar `[[ $string =~ ^" "*$ ]]` – kvantour Sep 08 '21 at 20:47
  • If I make a function `zblank` that returns either `1` or `0`, could I do the following: `[[ zblank "$s" ]] && printf '%s\n' "Option not used"`. – Angio Sep 08 '21 at 20:49
  • Only the parts you actually quote are treated literally. `[[ $string =~ ^\ *$ ]]` or `[[ $string %" "*$ ]]` would work. That said, a common workaround is to store the regex in a variable, then use an unquoted parameter expansion, like `regex="^ *$"; [[ $string =~ $regex ]]`. – chepner Sep 08 '21 at 20:53
  • @Angio With a function, you wouldn't use `[[ ]]` -- those allow you to use a conditional expression in place of a command, but a function is already a command. So just use `zblank "$s" && printf '%s\n' "Option not used"`. My answer [here](https://stackoverflow.com/questions/49849957/bash-conditional-based-on-exit-code-of-command/49850110#49850110) has more explanation of a similar situation. – Gordon Davisson Sep 08 '21 at 22:11
  • @GordonDavisson Actually `[[ zblank "$s" ]]` did not work but `[[ $(lg "$s") ]]` did work. Will check out your way. – Angio Sep 08 '21 at 22:28
  • @GordonDavisson To do the negation of the function command, how would I go with your strategy? – Angio Sep 08 '21 at 22:29
  • @Angio `[[ $(lg "$s") ]]` is testing whether `lg` prints anything, not checking its exit status. Is your function printing something, or `return`ing a status? Oh, and to negate it you'd use either `! zblank "$s" && printf ...` or `zblank "$s" || printf ...` – Gordon Davisson Sep 08 '21 at 22:48
  • Its purpose is more intended to be a status value. – Angio Sep 08 '21 at 23:05
  • @Angio In that case, there's something wrong with how the function's written. Try `zblank() { if [[ $string =~ ^" "*$ ]]; then return 0; else return 1; fi; }` or even simpler just `zblank() { [[ $1 =~ ^" "*$ ]]; }` (since functions implicitly return the status of the last command in them). – Gordon Davisson Sep 09 '21 at 00:10
  • That's what I've done. Using `zblank "$src" && printf '%s\n' "src used"` is still valid, am I correct? – Angio Sep 09 '21 at 00:26
  • You don't quote `$string` when using regular expressions? – Angio Sep 09 '21 at 01:36
  • @Angio It's not necesssary to quote variables inside `[[ ]]`, because there's no word splitting in that context. – Barmar Sep 09 '21 at 14:12
2

If you want it to be portable to non-bash shells (e.g. dash, which is the default /bin/sh on some Linux distros), you can use this:

if [ "$variable" = "${variable#*[![:space:]]}" ]; then

Explanation: ${variable%pattern} will remove a match of the pattern from the beginning of the variable's value if there is such a match. The pattern *[![:space:]] will match from the beginning of the string through the first non-space character (if there are any non-space characters in the string). Therefore, if there's at least one non-space character, the pattern will match and the variable's value will change, so the = test will fail. On the other hand if the string doesn't contain any non-space characters, the pattern won't match, the variable won't be modified, and the = test will succeed.

For completeness, you can also use case for this:

case "$variable" in
    *[![:space:]]* ) echo "variable is NOT empty" ;;
    * ) echo "variable IS empty" ;;
esac

Either of these should work in any POSIX-compliant shell.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • I did not know you could use character classes in a glob – kvantour Sep 08 '21 at 20:49
  • @kvantour Yep, according to [the POSIX specification](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01) bracket expressions in a glob are almost identical to in a regex. The only difference is you use `!` instead of `^` to negate the expression. – Gordon Davisson Sep 08 '21 at 20:56
-1

Use the exit status of grep -Pq '\S', which is true if there is at least 1 non-whitespace character, and false otherwise:

grep -Pq '\S' <<< " " && echo "not all whitespace" || echo "all whitespace"
# all whitespace

grep -Pq '\S' <<< "" && echo "not all whitespace" || echo "all whitespace" 
# all whitespace

grep -Pq '\S' <<< "a" && echo "not all whitespace" || echo "all whitespace"
# not all whitespace

Here, GNU grep uses the following options:
-P : Use Perl regexes.
-q : Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found.

\S : non-whitespace character.

SEE ALSO:
grep manual
perlre - Perl regular expressions

Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47