1

I have a 2 dimensional array in bash and I am able to successfully initialize it, but when I choose to set a value to a certain element in the array- the script sets it to ALL the elements in the array ! Baffled ! I was hoping it would set to just that one element.

Here's the scenario - I have a 2 D array for all the 12 months in a year. ( let's say for year 2012. ) I am able to initialize it to '-1'. Later at some time in the code I want to set just the 6th month to say 1000, then the 'initializing' sets the 1000 to all the elements in the array. Why ? I'm on a Mac OS Yosemite 10.10.5 and running this as /bin/bash

declare -a outages

function init_array () {
    for (( month=1; month<12; month++)) do
        outages[$month,12]=-1
    done
    outages[6,12]=1000 # this is setting all elements to 1000, instead of just 1.
}
function print_array () {

    for ((i=1;i<=12;i++)) do
         echo  outages[$i,12] = ${outages[$i,12]}
    done

}
init_array
print_array

Results Cut and Pasted below.
./test.sh 
outages[1,12] = 1000
outages[2,12] = 1000
outages[3,12] = 1000
outages[4,12] = 1000
outages[5,12] = 1000
outages[6,12] = 1000
outages[7,12] = 1000
outages[8,12] = 1000
outages[9,12] = 1000
outages[10,12] = 1000
outages[11,12] = 1000
outages[12,12] = 1000
Shashi Kiran
  • 63
  • 1
  • 7
  • Always try debugging with a `set -x` option in cases like these. If you had run like that it will be obvious for you to understand that you were operating the array as a one-dimensional entity. – Inian May 12 '16 at 08:40
  • If you need a 2-dimensional array (or even nested arrays), you are using the wrong language. – chepner May 12 '16 at 11:53

1 Answers1

2

Bash has two types of arrays: one-dimensional indexed and associative array. declare -a creates a one-dimensional array. To mimic a 2-D array, you need an associative array. Therefore, replace:

declare -a outages

with:

declare -A outages

With that one change:

$ bash test.sh
outages[1,12] = -1
outages[2,12] = -1
outages[3,12] = -1
outages[4,12] = -1
outages[5,12] = -1
outages[6,12] = 1000
outages[7,12] = -1
outages[8,12] = -1
outages[9,12] = -1
outages[10,12] = -1
outages[11,12] = -1
outages[12,12] =

(Note that outages[12,12] is never set because the loop is limited by month<12.)

What happens when you provide a 2-D subscript to a 1-D array

As in the original code, let's declare a 1-D array and try to use it as a 2-D array and see what happens:

$ declare -a outages
$ outages[1,2]=-1

This looks like it made a successful assignment to a 2-D array. But, that is not what happened. To see what really happened, use declare -p:

$ declare -p outages
declare -a outages='([2]="-1")'

We can see that element 2 was assigned a value, not element 1,2. The reason is that subscripts of 1-D arrays are treated as arithmetic expressions. Under the rules of arithmetic evaluation, anything before a comma is discarded.

One can see this by looking directly at an arithmetic expression:

$ echo $((1,2))
2

Everything before the , is discarded. That is why setting outages[6,12]=1000 had the effect of setting all your other ,12 elements to 1000.

Documentation

From the section of man bash entitled "ARITHMETIC EVALUATION":

The operators and their precedence, associativity, and values are the same as in the C language.

One needs to refer to C language documentation, such as here, to find out what the comma operator actually does.

Simulating 2-D array with bash 3.x

There are some hacks to shoehorn 2-D-like features into bash 3.x's 1-D arrays. See, for example, here or here. In this case, however, your subscripts are numeric and simpler approaches are possible. Let's update the code like this:

$ cat test2.sh
declare -a outages

function init_array () {
    for (( month=1; month<=12; month++)) do
        outages[$month+100*12]=-1
    done
    outages[6+100*12]=1000 # this is setting all elements to 1000, instead of just 1.
}
function print_array () {

    for ((i=1;i<=12;i++)) do
         echo  "outages[$i,12] = ${outages[$i+100*12]}"
    done

}
init_array
print_array

The code runs as you want:

$ bash test2.sh
outages[1,12] = -1
outages[2,12] = -1
outages[3,12] = -1
outages[4,12] = -1
outages[5,12] = -1
outages[6,12] = 1000
outages[7,12] = -1
outages[8,12] = -1
outages[9,12] = -1
outages[10,12] = -1
outages[11,12] = -1
outages[12,12] = -1

We can see what the array outages looks like to bash with declare -p:

$ declare -p outages
declare -a outages='([1201]="-1" [1202]="-1" [1203]="-1" [1204]="-1" [1205]="-1" [1206]="1000" [1207]="-1" [1208]="-1" [1209]="-1" [1210]="-1" [1211]="-1" [1212]="-1")'
Community
  • 1
  • 1
John1024
  • 109,961
  • 14
  • 137
  • 171
  • Thanks for the response. I am running this on Mac OS 10.10.5 Yosemite and declare -A is not recognized. ./test.sh: line 2: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...] outages[1,12] = 1000 bash -version GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc. – Shashi Kiran May 12 '16 at 09:11
  • Oops. Good point. As you correctly found, Bash 3.2 does not have associative arrays. They were added to [bash version 4](http://tldp.org/LDP/abs/html/bashver4.html) which was released in 2009. – John1024 May 12 '16 at 09:34
  • @ShashiKiran I just updated the answer with a workaround that might work for you. – John1024 May 12 '16 at 17:17
  • Thanks John - I voted for your answers and thanks for the help. Much appreciated. – Shashi Kiran May 14 '16 at 02:41