Bash – Strange behaviour of uninitialized arrays and unset arrays

arraybash

I'm writing a script and I discovered some unexpected behaviour of uninitialized and unset array variables that I do not understand.

First of all, the length:

$ echo ${#notset[@]}
0
$ uninitialized=
$ echo ${#uninitialized[@]}
1

Why is the uninitialized length 1? Shouldn't it be zero? Is it because a null variable is considered an array of one null element?

This fact leads to some problems. For example suppose I want to create an array, and insert a certain number of things based on the user command line arguments. I thought I could do something like(+):

myarray=

if [ some-condition ]
then
    myarray[${#myarray[@]}]=some-value
fi

if [ some-condition2 ]
then
    myarray[${#myarray[@]}]=some-value2
elif [ some-condition3 ]
then
    myarray[${myarray[@]}]=some-value3
    myarray[${myarray[@]}]=some-value4
fi

But this leaves the first slot to null, which I do not like and also breaks some code that I have written(*), and at this point suppose that I want to see if the array contains any element. How should I do it?

[ -z "${myarray[@]}" ]

Raises an error if the array contains more than an element.

[ -z "$myarray" ]

Fails because the first element is null, even if the array is not empty.

So, how should I control that an array is uninitialized?

And could someone explain what exactly happens when dealing with arrays and unset – uninitialized variables?


(+) I know that I could avoid "declaring" the variable and it would work, but this script will be reviewed by a professor, and he does not like variables being defined at random places.

(*) Before trying this thing I was keeping the length of the array in an other variable, and so I did not have problems. But I'd like to avoid defining this auxiliary variables since I know I can obtain the length without them.

Best Answer

You can see the difference with declare -p:

unset foo
declare -a foo
declare -p foo
# prints declare -a foo='()'
foo=
declare -p foo
# prints declare -a foo='([0]="")'

If you want to initialize an empty array, the output of the first declare -p is a good hint on the best way to declare it:

declare -a array='()'

(The declare -a part is probably optional, a simple array=() should work just as well.)

If you want to test if an array has 0 elements, use numeric comparison on ${#array[@]}; don't try to do a test -z on the expansion as won't give the correct result in many cases.

Related Question