How to copy a ksh associative array? - arrays

Is there a way to copy an associative array? I realize that regular arrays can be copied easily with a one liner as such:
set -A NEW_ARRAY $(echo ${OTHER_ARRAY[*]})
but doing so with associative arrays just gives you the values in that manner.
I know about nameref but I'm interested in knowing if there's a way of copying the array such that the original array isn't affected.

untested:
typeset -A NEW_ARRAY
for key in "${!OTHER_ARRAY[#]}"; do
NEW_ARRAY["$key"]="${OTHER_ARRAY[$key]}"
done
tested:
#!/usr/bin/ksh93
OTHER_ARRAY=( [Key1]="Val1" [Key2]="Val2" [Key3]="Val3" )
echo Keys: ${!OTHER_ARRAY[*]}
echo Values: ${OTHER_ARRAY[*]}
typeset -A NEW_ARRAY
for key in "${!OTHER_ARRAY[#]}"; do
NEW_ARRAY["$key"]="${OTHER_ARRAY[$key]}"
done
echo Keys: ${!NEW_ARRAY[*]}
echo Values: ${NEW_ARRAY[*]}
Result:
/home/exuser>./a
Keys: Key3 Key1 Key2
Values: Val3 Val1 Val2
Keys: Key3 Key1 Key2
Values: Val3 Val1 Val2

Related

How to create a associative array in a range, filled by given value?

How to create an associative array in a range, every position filled by the same given value ?
What's known or given:
Creating an associative array:
declare -A my_array
define range of array:
ug=-10
og=10
Value for filling:
filling_value=0
filling_value=0
ug=-10
og=10
unset my_array
declare -A my_array
for ((i=ug; i<=og; i++))
do
my_array[${i}]="${filling_value}"
done
Results:
$ typeset -p my_array
declare -A my_array=([10]="0" [-10]="0" [0]="0" [1]="0" [2]="0" [3]="0" [4]="0" [5]="0" [6]="0" [7]="0" [8]="0" [9]="0" [-7]="0" [-6]="0" [-5]="0" [-4]="0" [-3]="0" [-2]="0" [-1]="0" [-9]="0" [-8]="0" )
A slight variation that generates the same array:
for ((i=ug; i<=og; i++))
do
my_array+=( [${i}]="${filling_value}" )
done

Delete array elements ksh

I need to delete specific values in an array (which vary in their index positions), similar to the splice function in javascript.
Example:
set -A ARRAY1 "a" "b" "c" "d" "e"
set -A ARRAY2 "a" "c" "e"
# Expected ARRAY1 after splice: "b" "d"
What I've tried:
I iterated through the arrays to find matches to the values I want deleted, and set them to empty ("").
ITERATION=0
for i in "${ARRAY1[#]}"
do
for j in "${ARRAY2[#]}"
do
if [[ $i == $j ]]
then
ARRAY1[$ITERATION]=""
fi
done
ITERATION=$((ITERATION+1))
done
#ARRAY1 after emptying values: "" "b" "" "d" ""
After that, I made a variable to store the concatenation of the first array's values.
VARIABLE=${ARRAY1[#]}
Then set the array back together again.
set -A ARRAY1 $VARIABLE
# VARIABLE: b d
Now the ARRAY1 has 2 indexes with values "b" and "d" as expected.
echo "ARRAY1: ${ARRAY1[#]}"
# output: ARRAY1: b d
I tried searching for the correct way to do this but couldn't find anything, and I think my solution is not right, even if it seems to work. Is there a correct or better way to do this? Is there a function for this in ksh?
Thanks in advance!
So, what you want to do is take the difference of sets. An indexed array is not a good representation of a set. However, the keys of an associative array is.
Try this:
array1=( "a" "b" "c" "d" "e" )
array2=( "a" "c" "e" )
# declare an associative array
typeset -A tmp
# populate it
for elem in "${array1[#]}"; do
tmp[$elem]=1
done
# remove elements
for elem in "${array2[#]}"; do
unset "tmp[$elem]"
done
# reassign the array with the keys of the assoc. array
array1=( "${!tmp[#]}" )
printf "%s\n" "${array1[#]}"
b
d
Get out of the habit of using ALLCAPS variable names, leave those as reserved by the shell. One day you'll write PATH=something and then wonder why your script is broken.
Using the same notation as the OP, just need one small change to the if/then block to unset the array position:
ITERATION=0
for i in "${ARRAY1[#]}"
do
for j in "${ARRAY2[#]}"
do
if [[ $i == $j ]]
then
unset ARRAY1[$ITERATION]
fi
done
ITERATION=$((ITERATION+1))
done
Here's a ksh fiddle of the above.
A quick dump of the current array:
echo "ARRAY1: ${ARRAY1[#]}"
ARRAY1: b d

Pair two arrays into one associative array

I have two arrays:
SUBJECT_IDS=(44456 11123 77789)
DCM_FILES=("./77789/77789/"DICOM"" "./11123/11123/"DICOM"" "./44456/44456/77789/"DICOM"" )
Which I then sorted to make the indexing easier/already sort of 'pair' it by index:
IFS=$'\n'
sorted_SUBJECT_IDS=($(sort <<<"${SUBJECT_IDS[*]}"))
sorted_DCM_FILES=($(sort <<<"${DCM_FILES[*]}"))
How would I create an associative array by pairing the two arrays by indice?
headers=(
[11123]=./11123/11123/DICOM
[44456]=./44456/44456/DICOM
[77789]=./77789/77789/DICOM
)
Here are my attempts at creating an associative array:
Attempt 1:
declare -A headers
i=0
for i in "${sorted_SUBJECT_IDS[#]}"
do
headers["$i"]="${sorted_DCM_FILES[i]}";
echo "$i" : "${sorted_DCM_FILES[i]}"
((i++))
done
Output:
11123 :
44456 :
77789 :
Attempt 2:
typeset -A hash
hash=("$(#){sorted_SUBJECT_IDS:^sorted_DCM_FILES}")
Output:
bash: hash: "$(#){sorted_SUBJECT_IDS:^sorted_DCM_FILES}": mustuse subscript when assigning associative array
Your sort is not working the way you want it to be and moreover your array iteration should be by the index not by items.
You may use:
sorted_SUBJECT_IDS=($(sort -n <(printf '%s\n' "${SUBJECT_IDS[#]}")))
sorted_DCM_FILES=($(sort -n <(printf '%s\n' "${DCM_FILES[#]}")))
declare -A headers
for i in "${!sorted_SUBJECT_IDS[#]}"; do
headers["${sorted_SUBJECT_IDS[i]}"]="${sorted_DCM_FILES[i]}"
done
# check header associative array
declare -p headers
declare -A headers=([77789]="./77789/77789/DICOM" [44456]="./44456/44456/77789/DICOM" [11123]="./11123/11123/DICOM" )

How to assign an associative array to another variable in zsh?

In zsh, is there a way to assign an associative array to another variable? I would like to to something like this:
typeset -A orig
orig=(key1 val1 key2 val2)
typeset -A other
other=$orig
print '$orig: '$orig
print '$other: '$other
print '$orig[key1]: '$orig[key1]
print '$other[key1]: '$other[key1]
This will print:
$orig: val1 val2
$other: val1 val2
$orig[key1]: val1
$other[key1]:
I want to be able to use $other[key1] and get val1.
I know I can iterate over the keys and copy it item by item, but I really want to avoid this. Also, eval is evil :)
I have tried other=($orig) and other variations, but this will get my values from orig and create as associative array like this
other=(val1 val2)
So other[key1] returns nothing and other[val1] returns val2, which is not what I want.
If I understand correctly, what is going on in every attempt of mine is that $other is getting an array of the values of $orig, without the keys. How can I make it receive both keys and values and have the correct association between them?
I'm not worried about null values, if that would even be a problem,
because I am sure $orig will be well behaved.
Thanks!
You have to delve into the wonderful world of parameter expansion flags :) The k and v flags can be used together to force an associative array to expand to both its keys and values.
$ typeset -A orig
$ orig=(key1 val1 key2 val2)
$ print ${(kv)orig}
key1 val1 key2 val2
Then you can use the set command to populate your copy with the alternating key/values produced by that expansion.
$ typeset -A other
$ set -A other ${(kv)orig}
$ print $other[key1]
val1
These and other flags are documented in man zshexpn under "Parameter Expansion Flags", which is one of my favorite zsh features.
zsh: bad set of key/value pairs for associative array
Perfect world without escaping:
typeset -A old new
old=(k1 v1 k2 v2 k3 v3)
typeset old # old=( k1 v1 k2 v2 k3 v3 )
...doesn't exist and your arrays usually include empty values:
old[k2]=
typeset old # old=( k1 v1 k2 '' k3 v3 )
...therefore you need to use " (quoting), # (escaping) and f (field separation):
typeset new # new=( )
new=("${(#fkv)old}")
typeset new # new=( k1 v1 k2 '' k3 v3 )
See man zshexpn for more on parameter expansion flags.

foreach loop through associative array in bash only returns last element

This should print the whole associative array to the console:
#!/bin/sh
declare -a array=([key1]='value1' [key2]='value2')
for key in ${!array[#]}; do
echo "Key = $key"
echo "Value = ${array[$key]}"
done
echo ${array[key1]}
echo ${array[key2]}
Instead it prints oly the last variable:
[mles#sagnix etl-i_test]$ ./test.sh
Key = 0
Value = value2
value2
value2
Where is my fault?
#htor:
Bash Version is 3.2.25(1)-release.
Associative arrays are supported in Bash 4 and newer versions. An array declared with the -a option is just a regular array that can be indexed by integers, not keys. This declaration results in the array with one element value2. When iterating over the keys with for key in ${!array[#]} the value of $key is 0 and therefore you get the first element.
Given the error output you get when trying to use -A to declare to array, I assume your Bash version is older than 4. Inspect the variable $BASH_VERSION.
For a deeper explaination of arrays, see http://mywiki.wooledge.org/BashGuide/Arrays.
#!/bin/bash
declare -A array=([key1]='value1' [key2]='value2')
for key in ${!array[#]}; do
echo "array[$key] = ${array[$key]}"
done
echo ${array[key1]}
echo ${array[key2]}

Resources