0

Hello StackExchange pros!

I am working on a zsh project for macOS. I used typeset to create three associative arrays to hold values, and a fourth array to reference the individual arrays. Is it possible to iterate over the arrCollection to retrieve the key/value pairs from each of the member arrays? Note that the keys in the arrays below are not the same as my production script--they are simply key indices rather than the more descriptive keys you might find in an associative array.

I thought I could use parameter expansion like this:

for k in $(sort <<< "${(kvF)arrCollection}"); do
   echo "$arrCollection["${(kvF)k}"]"
done

I don't have it quite right though. Can anyone help? Expected output will be a list of all items from all three arrays separated by a newline.

Full script sample below. Usage: arrTest.sh showAll

#!/bin/zsh

key=$1

typeset -A arrOne arrTwo arrThree
typeset -A arrCollection

#Plan is to use an array of arrays so that a for loop can be used later to loop
#through each key/value pair looking for a value that matches some pattern in an if statement
#(if statement not included here). In the showAll case, how can I use parameter expansion to print all
#of the values in each array? The if statement will further constrict what is actually echoed based on its
#value.

arrOne[1]="First"
arrOne[2]="Second"
arrOne[3]="Third"
arrOne[4]="Fourth"

arrTwo[1]="Purple"
arrTwo[2]="Orange"
arrTwo[3]="Red"
arrTwo[4]="Green"
arrTwo[5]="Blue"

arrThree[1]="First"
arrThree[2]="Red"
arrThree[3]="Planet"
arrThree[4]="Sun"
arrThree[5]="Moon"
arrThree[6]="Star"

#Array of arrays
arrCollection[1]=arrOne
arrCollection[2]=arrTwo
arrCollection[3]=arrThree

#Expect a parameter
if [ -z "$key" ]
then
    echo "Please enter a parameter"
else
    case "$key" in
    showAll)
        for k in $(sort <<< "${(kvF)arrCollection}"); do
            #This is the part I am having trouble with
            echo "$arrCollection["${(kvF)k}"]"
        done
        exit 1
    ;;
    *)
        echo "Something goes here"
        exit 1
    ;;
    esac
fi
1
  • 1
    The keys of your associative arrays seem to be decimal integers and in sequence and starting from 1. Why not use plain arrays instead? – Stéphane Chazelas Apr 26 at 16:33
2

Not sure why you're using associative arrays instead of plain arrays when your keys are sequential decimal numbers starting from one, but if I understand what you want correctly, you could do:

for key in "${(nok@)arrCollection}"; do
  print -r - "Assoc $key: $arrCollection[$key]"
  printf ' "%s" => "%s"\n' "${(@kvP)arrCollection[$key]}"
done

Which on your sample should give something like:

Assoc 1: arrOne
 "3" => "Third"
 "4" => "Fourth"
 "1" => "First"
 "2" => "Second"
Assoc 2: arrTwo
 "3" => "Red"
 "4" => "Green"
 "5" => "Blue"
 "1" => "Purple"
 "2" => "Orange"
Assoc 3: arrThree
 "3" => "Planet"
 "4" => "Sun"
 "5" => "Moon"
 "6" => "Star"
 "1" => "First"
 "2" => "Red"

Or to also sort members of each assoc numerically on keys:

for key in "${(nok@)arrCollection}"; do
  print -r - "Assoc $key: $arrCollection[$key]"
  for assoc_key in "${(@knoP)arrCollection[$key]}"; do
    printf ' "%s" => "%s"\n' "$assoc_key" "${${(P)arrCollection[$key]}[$assoc_key]}"
  done
done

Which would give:

Assoc 1: arrOne
 "1" => "First"
 "2" => "Second"
 "3" => "Third"
 "4" => "Fourth"
Assoc 2: arrTwo
 "1" => "Purple"
 "2" => "Orange"
 "3" => "Red"
 "4" => "Green"
 "5" => "Blue"
Assoc 3: arrThree
 "1" => "First"
 "2" => "Red"
 "3" => "Planet"
 "4" => "Sun"
 "5" => "Moon"
 "6" => "Star"

Keys above are sorted using the o (order) and n (numerical) parameter expansion flags. With those, elements are compared using locale collation order except for the parts that are made of sequences of decimal digits¹. Sequence of decimal digits are interpreted a decimal integer numbers and compared numerically.

That's similar to what the GNU implementation of sort does with the -V/--version-sort option.

So foo2-3 would come before foo10-3 and before foo2-10, for instance but that can't be used for numbers other than positive decimal integers (-2 would also come before -10 and 2.3 before 2.10).

To sort using other arbitrary criteria, while there are convoluted ways to do it in a builtin fashion in zsh, it may be easier to resort to sort if you can guarantee the elements don't contain NL or NUL characters and are not empty.

For instance with GNU sort:

for key in ${(0)"$(print -rNC1 -- ${(k)arrCollection} | sort -zg)"}

To loop over the non-empty keys numerically, but this time supporting all sorts of number representations (including 0x20, 1e-1, infinity, -20, 0x3.fp5...).

Above that's using NUL delimited records to feed the list to sort and split its output, so assumes keys don't contain NUL characters. To use NL/LF characters instead, remove the -N, -z and change (0) to (f).


¹ and NUL characters which have to be handled separately as the standard comparison API chokes on it.

5
  • Phenomenal! Thank you for helping out. The arrays in my sample script here were simplified. In my master script the keys have string values like -1000 and -1100. I wanted to simplify until I understood the syntax. – jrg Apr 26 at 17:07
  • Beware ${(no)keys} above for numeric ordering would be more like GNU sort -V, it would sort -2 after -1 as the numeric component of that sorting only considers the sequences of decimal digits in the strings to compare. – Stéphane Chazelas Apr 26 at 17:11
  • To help clarify that 'Beware ...' comment, when sorting, it's an ASCII sort, NOT a NUMERIC sort. The ASCII codes for '-' compare equally, so it next checks the ASCII codes for '1' and '2', which '1' comes before '2', so ASCII '-1' comes before ASCII '-2'. – C. M. Apr 27 at 10:46
  • @C.M., not with both n and o it's a strcoll() + numeric sort in that foo comes before FOZ and foo2 comes before foo10, but only the sequences of decimal digits are considered for the numeric component of that sorting. -2 comes before -10 just like foo2 comes before foo10. In any case, outside the C/POSIX locale, that has little to do with the order of characters in the ASCII charset. – Stéphane Chazelas Apr 27 at 10:54
  • Thank you Stéphane..the sort tip was helpful as well. For some of the arrays I ended up sorting with -V and for others I used -nr due to the content in each. Result was content that users would think was natural looking. – jrg Apr 27 at 12:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.