2

If I want a 2D bash array, is there any recommended way of doing this for say, a N x M arrangement? On several occasions, I've had to define array1, array2, arrayN each of M elements in conjunction with case statements - but this is very clumsy.

I realise it would be possible to define a single big array X and compute a 1-1 map between X[something] and the [i,j] element - but this seems like a lot of work to do it properly.

Does there exist any set of functions which could do this reasonably efficiently? I realise its a bit like making Bash imitate FORTRAN.

7
  • 1
    You can do the same thing awk does: emulate multi-dim arrays with single-dim associative arrays. Eg. declare -A a; i=2; j=3; a[$i,$j]=4; echo ${a[2,3]}. Of course, nicely iterating through rows and cols will be a bitch. But if you need to handle complex data structures, using bash is certainly not the best idea.
    – user313992
    Commented Oct 21, 2020 at 21:31
  • 1
    Or emulate them with indexed arrays: declare -a a; i=2; j=3; a[i<<16|j]=4; echo ${a[2<<16|3]} (assuming that 65536 x 65536 is enough for everybody ;-)).
    – user313992
    Commented Oct 21, 2020 at 21:39
  • 1
    In case it wasn't clear, the indexed arrays in bash are sparse; a[1<<30]=foo will NOT allocate 1G of empty slots. And like everything in bash, they're also very slow ;-)
    – user313992
    Commented Oct 21, 2020 at 22:00
  • 3
    The shell is not the type of language you usually want to use for things requiring multi-dimensional arrays. It may be more efficient to move to some other scripting language, such as Python, Perl, or any common compiled language.
    – Kusalananda
    Commented Oct 22, 2020 at 6:17
  • 2
    @JeremyBoden If speed is not an issue, then why not just emulate a multi-dimensional array in a 1-dimensional array with a(i,j) == a[i*ncols + j] or similar transformation? This is a common way to do it when managing 1D structures is easier (or more efficient) than managing multi-D structures.
    – Kusalananda
    Commented Oct 23, 2020 at 13:12

2 Answers 2

3

You could always switch to ksh93 (whose syntax bash has borrowed in the first place for most things) which has multidimensional arrays.

var=(
  (a b)
  (c d)
)

Use as:

$ print -r -- "${var[0][1]}"
b

To loop over all the elements:

for i in "${!var[@]}"; do
  for j in "${!var[$i][@]}"; do
    print -r "var[$i,$j]=${var[$i][$j]}"
  done
done
1
  • If anybody has to fight the bash shell, it's because it's supposedly present scarequotes-"everywhere". If one has the luxury to switch to ksh93, they could also switch to APL or J ;-)
    – user313992
    Commented Oct 23, 2020 at 12:23
2

You can emulate multidimensional arrays with one-dimensional associative arrays (the way awk does it [1]), or with indexed arrays, by shifting the index.

The problem is that you cannot iterate through them in an obvious, natural way.

declare -A a  # associative array
i=2; j=3; a[$i,$j]=4; echo ${a[2,3]}

unset a
declare -a a  # indexed array, the default
i=2; j=3; a[i<<16|j]=4; echo ${a[2<<16|3]}

Notice that the indexed arrays in bash are sparse; a[1<<30]=1 will NOT create 1Gi - 1 empty slots, neither in actual RAM, nor in the virtual address space. Also, since bash's indexed arrays are implemented as doubly-linked lists, random access to an element is slower than with associative arrays (which are using the same hash table implementation bash is using for caching the paths of external commands).

[1] Awk automatically turns an expression like a[x,y,z] into a[x SUBSEP y SUBSEP z] -- where the default value of SUBSEP is \x1c (^\), a control character not supposed to appear in keys. GNU awk also has real multidimensional arrays as a non-standard extension. The classical awk multi-dim array emulation is also available in perl ($; => SUBSEP).

You must log in to answer this question.

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