Search code examples
arrayslinuxbashshelllocal-variables

Local array variables in Bash 3.0


Intro

In Bash I can instantiate an empty array a using a=(). I can then append elements using e.g. a[${#a[@]}]="new element" where ${#a[@]} gives the number of elements in a. In a Bash function:

fun() {
    local a=()
    echo "size: ${#a[@]}"
    echo "content: >${a[@]}<"
}
fun  # run the function

In modern Bash versions, this prints

size: 0
content: ><  # empty

Problem

For Bash 3.0, though, I get

size: 1
content: >()<

It seems as though a is not really an array but rather a string with the value ().

I can get the usual behavior if I remove the local keyword. This suggests that mixing local and array initialization () is not supported in Bash 3.0.

Is this expected?

I am writing a large Bash script and seek to be compatible with all Bash versions down to 3.0. I can think of two solutions, neither of which are nice:

  1. Only use global array variables.
  2. Ignore the first element of a if this is equal to (), once a has been fully populated with elements. This (somewhat surprisingly) works because even when a is the string (), array appends a[${#a[@]}]="new element" still works, effectively converting a to an array the original string value () as the first element.

Any better ideas?

Test

To test things out with Bash 3.0, I use Docker:

docker run --rm bash:3.0 bash -c 'fun() { local a=(); echo "size: ${#a[@]}"; echo "content: >${a[@]}<"; }; fun'

The problem goes away already wih bash:3.1.


Solution

  • Assignment of an "empty" array to an name doesn't actually define a variable at all; it just sets the array attribute of the name. Some options:

    • declare -a a
    • local a; a=()
    • local -a a

    What you are seeing appears to be a bug in bash 3.0's implementation of local (subsequently fixed in bash 3.1) that doesn't properly recognize array assignments.