86

I have an array which gets filled with different error messages as my script runs.

I need a way to check if it is empty of not at the end of the script and take a specific action if it is.

I have already tried treating it like a normal VAR and using -z to check it, but that does not seem to work. Is there a way to check if an array is empty or not in Bash?

Thanks

116

Supposing your array is $errors, just check to see if the count of elements is zero.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi
  • 10
    Please note that = is a string operator. It happens to work fine in this case, but I'd use the proper arithmetic operator -eq instead (just in case I'd want to switch to -ge or -lt, etc.). – musiphil Nov 8 '13 at 19:27
  • 5
    Does not work with set -u: "unbound variable" - if the array is empty. – Igor Apr 8 '17 at 19:56
  • @Igor: Works for me in Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. If I unset foo, then it prints foo: unbound variable, but that's different: the array variable doesn't exist at all, rather than existing and being empty. – Peter Cordes Sep 17 '17 at 4:22
  • Also tested in Bash 3.2 (OSX) when using set -u - as long as you declared your variable first, this works perfectly. – zeroimpl Aug 5 '18 at 16:48
7

You can also consider the array as a simple variable. In that way, just using

if [ -z "$array" ]; then
    echo "Array empty"
else
    echo "Array non empty"
fi

or using the other side

if [ -n "$array" ]; then
    echo "Array non empty"
else
    echo "Array empty"
fi

The problem with that solution is that if an array is declared like this: array=('' foo). These checks will report the array as empty, while it is clearly not. (thanks @musiphil!)

Using [ -z "$array[@]" ] is clearly not a solution neither. Not specifying curly brackets tries to interpret $array as a string ([@] is in that case a simple literal string) and is therefore always reported as false: "is the literal string [@] empty?" Clearly not.

  • 6
    [ -z "$array" ] or [ -n "$array" ] doesn't work. Try array=('' foo); [ -z "$array" ] && echo empty, and it will print empty even though array is clearly not empty. – musiphil Aug 17 '15 at 23:25
  • 2
    [[ -n "${array[*]}" ]] interpolates the entire array as a string, which you check for non-zero length. If you consider array=("" "") to be empty, rather than having two empty elements, this might be useful. – Peter Cordes Sep 19 '17 at 15:21
5

I generally use arithmetic expansion in this case:

if (( ${#a[@]} )); then
    echo not empty
fi
  • Nice and clean! I like it. I also note that if the first element of the array is always nonempty, (( ${#a} )) (length of the first element) will work also. However, that will fail on a=(''), whereas (( ${#a[@]} )) given in the answer will succeed. – cxw Aug 15 '18 at 12:36
2

I checked it with bash-4.4.0:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]} ]]; then
        echo not empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

and bash-4.1.5:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]:+${array[@]}} ]]; then
        echo non-empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

In the latter case you need the following construct:

${array[@]:+${array[@]}}

for it to not fail on empty or unset array. That's if you do set -eu like I usually do. This provides for more strict error checking. From the docs:

-e

Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists), or a compound command (see Compound Commands) returns a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command’s return status is being inverted with !. If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. A trap on ERR, if set, is executed before the shell exits.

This option applies to the shell environment and each subshell environment separately (see Command Execution Environment), and may cause subshells to exit before executing all the commands in the subshell.

If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.

-u

Treat unset variables and parameters other than the special parameters ‘@’ or ‘*’ as an error when performing parameter expansion. An error message will be written to the standard error, and a non-interactive shell will exit.

If you don't need that, feel free to omit :+${array[@]} part.

Also do note, that it's essential to use [[ operator here, with [ you get:

$ cat 1.sh
#!/usr/bin/env bash
set -eu
array=(a b c d)
if [ "${array[@]}" ]; then
    echo non-empty
else
    echo empty
fi

$ ./1.sh
_/1.sh: line 4: [: too many arguments
empty
  • With -u you should actually use ${array[@]+"${array[@]}"} cf stackoverflow.com/a/34361807/1237617 – Jakub Bochenski Sep 13 '17 at 14:52
  • @JakubBochenski Which version of bash are you talking about? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9 – x-yuri Sep 13 '17 at 18:28
  • The problem in the single brackets example is the @, surely. You could use * array expansion like [ "${array[*]}" ], could you not? Still, [[ also works fine. The behaviour of both of these for an array with multiple empty strings is a little surprising. Both [ ${#array[*]} ] and [[ "${array[@]}" ]] are false for array=() and array=('') but true for array=('' '') (two or more empty strings). If you wanted one or more empty strings to all give true, you could use [ ${#array[@]} -gt 0 ]. If you wanted them all false, you could maybe // them out. – eisd Sep 2 '18 at 6:42
  • @eisd I could use [ "${array[*]}" ], but if I were to run into such expression, it would be harder for me to understand what it does. Since [...] operates in terms of strings on the result of interpolation. As opposed to [[...]], which can be aware of what was interpolated. That is, it can know that it was passed an array. [[ ${array[@]} ]] reads to me as "check if array is non-empty", while [ "${array[*]}" ] as "check if result of interpolation of all array elements is a non-empty string". – x-yuri Sep 2 '18 at 15:30
  • ...As for behavior with two empty strings it is not at all surprising to me. What is surprising is the behavior with one empty string. But arguably reasonable. Regarding [ ${#array[*]} ], you probably meant [ "${array[*]}" ], since the former is true for any number of elements. Because number of elements is always non-empty string. Regarding the latter with two elements, the expression inside brackets expands to ' ' which is non-empty string. As for [[ ${array[@]} ]], they just think (and rightly so) that any array of two elements is non-empty. – x-yuri Sep 2 '18 at 15:46
0

In my case, the second Answer was not enough because there could be whitespaces. I came along with:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi
  • echo | wc seems needlessly inefficient compared using shell built-ins. – Peter Cordes Sep 17 '17 at 4:16
  • Not sure if I understand @PeterCordes, can I modify the second answers' [ ${#errors[@]} -eq 0 ]; in a way to work around the whitespace problem? I'd also prefer the built-in. – Micha Sep 18 '17 at 13:10
  • How exactly does whitespace cause a problem? $# expands to a number, and works fine even after opts+=(""). e.g. unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}" and I get 2. Can you show an example of something that doesn't work? – Peter Cordes Sep 18 '17 at 13:33
  • It's long time ago. IIRC the originating source always printed at least "". Thus, for opts="" or opts=("") I needed 0, not 1, ignoring the empty newline or empty string. – Micha Sep 19 '17 at 14:20
  • Ok, so you need to treat opts=("") the same as opts=()? That's not an empty array, but you could check for empty array or empty first element with opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Note that your current answer says "no options" for opts=("" "-foo"), which is totally bogus, and this reproduces that behaviour. You could [[ -z "${opts[*]}" ]] I guess, to interpolate all the array elements into a flat string, which -z checks for non-zero length. If checking the first element is sufficient, -z "$opts" works. – Peter Cordes Sep 19 '17 at 15:18
0

I prefer to use double brackets:

if [[ !${array[@]} ]]
then
    echo "Array is empty"
else
    echo "Array is not empty"
fi

Double Brackets: https://stackoverflow.com/questions/669452/is-preferable-over-in-bash

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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