207

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?

9 Answers 9

260

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
4
  • 17
    Does not work with set -u: "unbound variable" - if the array is empty.
    – user97619
    Apr 8, 2017 at 19:56
  • 1
    @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. Sep 17, 2017 at 4:22
  • 1
    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, 2018 at 16:48
  • this one is more compatible for most shells, but found this arithmetic way also stackoverflow.com/a/65957757/1091853 Sep 21, 2023 at 16:30
60

I generally use arithmetic expansion in this case:

if (( ${#a[@]} )); then
    echo not empty
fi
5
  • 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, 2018 at 12:36
  • It might have been so once, but I can't reproduce it. If echo $BASH_VERSION && a=('') && (( ${#a[@]} )) && echo not empty || echo empty happens to output "empty," fell free to tell your version.
    – x-yuri
    Dec 17, 2020 at 21:38
  • @x-yuri it works for me: if I run that exact command, the output is 5.0.17(1)-release / not empty
    – Brandon
    Dec 15, 2021 at 4:09
  • This is the correct answer. You can also write it as (( ${#a[@]} != 0 )) if you prefer to make the non-zero check explicit.
    – dimo414
    Apr 14, 2023 at 6:24
  • ok yeah, see that one here that @dimo414 mentioned stackoverflow.com/a/65957757/1091853 Sep 21, 2023 at 16:31
12

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.

8
  • 9
    [ -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, 2015 at 23:25
  • 4
    [[ -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. Sep 19, 2017 at 15:21
  • 1
    @Michael: Crap, you're right. It only works with a 1-element array of an empty string, not 2 elements. I even checked older bash and it's still wrong there; like you say set -x shows how it expands. I guess I didn't test that comment before posting. >.< You can make it work by setting IFS='' (save/restore it around this statement), because "${array[*]}" expansion separates elements with the first character of IFS. (Or space if unset). But "If IFS is null, the parameters are joined without intervening separators." (docs for $* positional params, but I assume same for arrays). Jun 18, 2019 at 16:57
  • 3
    This answer is straight up wrong, why does it have 8 points? It believes the array is incorrectly empty for many different cases
    – Shardj
    Jul 24, 2019 at 14:22
  • 2
    this is only check if the first element is empty, not the array.
    – kdubs
    Jan 20, 2023 at 20:52
9

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
5
  • With -u you should actually use ${array[@]+"${array[@]}"} cf stackoverflow.com/a/34361807/1237617 Sep 13, 2017 at 14:52
  • @JakubBochenski Which version of bash are you talking about? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
    – x-yuri
    Sep 13, 2017 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, 2018 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, 2018 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, 2018 at 15:46
3

If you want to detect an array with empty elements, like arr=("" "") as empty, same as arr=()

You can paste all the elements together and check if the result is zero-length. (Building a flattened copy of the array contents is not ideal for performance if the array could be very large. But hopefully you aren't using bash for programs like that...)

But "${arr[*]}" expands with elements separated by the first character of IFS. So you need to save/restore IFS and do IFS='' to make this work, or else check that the string length == # of array elements - 1. (An array of n elements has n-1 separators). To deal with that off-by-one, it's easiest to pad the concatenation by 1

arr=("" "")

## Assuming default non-empty IFS
## TODO: also check for ${#arr[@]} -eq 0
concat="${arr[*]} "      # n-1 separators + 1 space + array elements
[[ "${#concat}" -ne "${#arr[@]}" ]]  && echo not empty array || echo empty array

test case with set -x

### a non-empty element
$ arr=("" "x")
  + arr=("" "x")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat=' x '
  + [[ 3 -ne 2 ]]
  + echo not empty array
not empty array

### 2 empty elements
$ arr=("" "")
  + arr=("" "")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat='  '
  + [[ 2 -ne 2 ]]
  + echo empty array
empty array

Unfortunately this fails for arr=() : [[ 1 -ne 0 ]]. So you'd need to check for actually empty arrays first separately.


Or with IFS=''. Probably you'd want to save/restore IFS instead of using a subshell, because you can't get a result out of a subshell easily.

# inside a () subshell so we don't modify our own IFS
(IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)

example:

$ arr=("" "")
$ (IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)
   + IFS=
   + [[ -n '' ]]
   + echo empty array
empty array

does work with arr=() - it's still just the empty string.

2
  • I upvoted, but I've started using [[ "${arr[*]}" = *[![:space:]]* ]], since I can count on at least one non-WS character. Jun 18, 2019 at 18:30
  • @Michael: yup, that's a good option if you don't need to reject arr=(" "). Jun 18, 2019 at 20:59
2

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

Edited in order to work as expected

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
8
  • echo | wc seems needlessly inefficient compared using shell built-ins. Sep 17, 2017 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, 2017 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? Sep 18, 2017 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, 2017 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. Sep 19, 2017 at 15:18
0

Sadly I cannot comment yet - Regarding @Nick Tsai - I didn't work for me but the following:

if [[ ! ${arrayName[*]} ]]; then ...
if [[ ! ${arrayOne[*]} && ! ${arrayTwo[*]} ]]; then ...

spacing is important for this to work!

-1

You could also benefit from jq if you got it installed on your system:

if [[ $(echo $array | jq -r 'length') -eq 0 ]]
then
    echo "Array is empty"
else
    echo "Array is not empty"
fi

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .