How to combine associative arrays in bash?

Does anyone know of an elegant way to combine two associative arrays in bash , like a regular array? This is what I am talking about:

In bash, you can combine two regular arrays as follows:

 declare -ar array1=( 5 10 15 ) declare -ar array2=( 20 25 30 ) declare -ar array_both=( ${array1[@]} ${array2[@]} ) for item in ${array_both[@]}; do echo "Item: ${item}" done 

I want to do the same with two associative arrays, but the following code does not work:

 declare -Ar array1=( [5]=true [10]=true [15]=true ) declare -Ar array2=( [20]=true [25]=true [30]=true ) declare -Ar array_both=( ${array1[@]} ${array2[@]} ) for key in ${!array_both[@]}; do echo "array_both[${key}]=${array_both[${key}]}" done 

It produces the following error:

./associative_arrays.sh: line 3: array_both: true: should use the index when assigning an associative array

The following is the work I came across:

 declare -Ar array1=( [5]=true [10]=true [15]=true ) declare -Ar array2=( [20]=true [25]=true [30]=true ) declare -A array_both=() for key in ${!array1[@]}; do array_both+=( [${key}]=${array1[${key}]} ) done for key in ${!array2[@]}; do array_both+=( [${key}]=${array2[${key}]} ) done declare -r array_both for key in ${!array_both[@]}; do echo "array_both[${key}]=${array_both[${key}]}" done 

But I was hoping that in fact I missed some kind of grammar that would allow one-line assignment, as shown in a non-working example.

Thanks!

+6
source share
3 answers

I don’t have a one-line interface either, but there is another “workaround” here that someone might like to use string conversion. These are 4 lines, so I am only 3 half-columns from the desired answer!

 declare -Ar array1=( [5]=true [10]=true [15]=true ) declare -Ar array2=( [20]=true [25]=true [30]=true ) # convert associative arrays to string a1="$(declare -p array1)" a2="$(declare -p array2)" #combine the two strings trimming where necessary array_both_string="${a1:0:${#a1}-3} ${a2:21}" # create new associative array from string eval "declare -A array_both="${array_both_string#*=} # show array definition for key in ${!array_both[@]}; do echo "array_both[${key}]=${array_both[${key}]}" done 
+2
source
 #!/bin/bash function merge_hashes() { local -n DEST_VAR=$1 shift local -n SRC_VAR local KEY for SRC_VAR in $@ ; do for KEY in "${!SRC_VAR[@]}"; do DEST_VAR[$KEY]="${SRC_VAR[$KEY]}" done done } declare -Ar array1=( [5]=true [10]=true [15]=true ) declare -Ar array2=( [20]=true [25]=true [30]=true ) declare -A array_both=() # And here comes the one-liner: merge_hashes array_both array1 array2 declare -p array_both 
0
source

The main reason your second attempt does not work is because you are trying to solve another problem using the same solution.

In the first dataset, you have two numerical indexed arrays, where the keys have no meaning except perhaps the order in which they appear, and their values ​​are really important. I interpreted this as meaning that you want the linear concatenation of these values ​​to a new array with a new index that discards the previous keys but retains the original order of the elements, as well as the order in which you passed them.

In the second dataset, you have two associative indexed arrays where the keys are values ​​and the values ​​are really just placeholders. I noticed that you used numeric keys, which, if you want to use numeric indexed arrays, will allow you to keep both the order of the values ​​and the order of the keys, assuming that you want the keys in ascending order ...

So, to solve these problems, I have 3 convenient functions that I wrote that use declare and eval to speed up the merging / merging of large arrays, rather than using loops to assign each of them. They also take a variable number of arrays as an argument, so you can join / merge / drop as many as you want.

NOTE. I changed the value / key “30” to “30 30” to demonstrate how the string will behave differently than the number in some cases.

 join_arrays(){ # <array> [<array> ...] <destination array> # linear concatenates the values, re-keys the result. # works best with indexed arrays where order is important but index value is not. local A_; while (( $# > 1 )); do A_+="\"\${$1[@]}\" "; shift; done eval "$1=($A_)"; } # This works by building and running an array assignment command # join_array a1 a2 a3 becomes a3=("${a1[@]" "$a2[@]" ); merge_arrays(){ # <array> [<array> ...] <destination array> # merges the values, preserves the keys. # works best with assoc arrays or to obtain union-like results. # if a key exists in more than one array the latter shall prevail. local A_ B_; while (( $# > 1 )); do B_=`declare -p $1`; B_=${B_#*=??}; A_+=${B_::-2}" "; shift; done eval "$1=($A_)"; } # this crops the output of declare -p for each array # then joining them into a single large assignment. # try putting "echo" in front of the eval to see the result. dump_arrays(){ # <array> [<array> ...] # dumps array nodes in bash array subscript assignment format # handy for use with array assignment operator. Preseves keys. # output is a join, but if you assign it you obtain a merge. local B_; while (( $# > 0 )); do B_=`declare -p $1`; B_=${B_#*=??}; printf "%s " "${B_::-2}"; shift; done } # same as above but prints it instead of performing the assignment # The data sets, first the pair of indexed arrays: declare -a array1=( 5 10 15 ); declare -a array2=( 20 25 "30 30" ); # then the set of assoc arrays: declare -a array3=( [5]=true [10]=true [15]=true ); declare -a array4=( [20]=true [25]=true ["30 30"]=true ); # show them: declare -p array1 array2 array3 array4; # an indexed array for joins and an assoc array for merges: declare -a joined; declare -A merged; # the common way to join 2 indexed arrays' values: echo "joining array1+array2 using array expansion/assignment:"; joined=( "${array1[@]}" "${array2[@]}" ); declare -p joined; 

declare -a join = '([0] = "5" [1] = "10" [2] = "15" [3] = "20" [4] = "25" [5] = "30 30" ) '

 # this does exactly the same thing, mostly saves me from typos ;-) echo "joining array1+array2 using join_array():"; join_arrays array1 array2 joined; declare -p joined; 

declare -a join = '([0] = "5" [1] = "10" [2] = "15" [3] = "20" [4] = "25" [5] = "30 30" ) '

 # this merges them by key, which is inapropriate for this data set # But I've included it for completeness to contrast join/merge operations echo "merging array1+array2 using merge_array():"; merge_arrays array1 array2 merged; declare -p merged; 

declare -A merged = '([0] = "20" [1] = "25" [2] = "30 30")'

 # Example of joining 2 associative arrays: # this is the usual way to join arrays but fails because # the data is in the keys, not the values. echo "joining array3+array4 using array expansion/assignment:" joined=( "${array3[@]}" "${array4[@]}" ); declare -p joined; 

declare -a join = '([0] = "true" [1] = "true" [2] = "true" [3] = "true" [4] = "true" [5] = "true")

 # and again, a join isn't what we want here, just for completeness. echo "joining array3+array4 using join_array():"; join_arrays array3 array4 joined; declare -p joined; 

declare -a join = '([0] = "true" [1] = "true" [2] = "true" [3] = "true" [4] = "true" [5] = "true")

 # NOW a merge is appropriate, because we want the keys! echo "merging array3+array4 using merge_array():" merge_arrays array3 array4 merged; declare -p merged; 

declare -A merged = '([25] = "true" [20] = "true" ["30 30"] = "true" [10] = "true" [15] = "true" [5] = " true ") '

 # Bonus points - another easy way to merge arrays (assoc or indexed) by key # Note: this will only work if the keys are numeric... join_arrays array1 array2 joined; # error expected because one keys is "30 30" ... eval joined+=(`dump_arrays merged`); 

bash: 30 30: syntax error in expression (error token is "30")

 declare -p joined 

declare -a join = '([0] = "5" [1] = "10" [2] = "15" [3] = "20" [4] = "25" [5] = "30 30" [20] = "true" [25] = "true") '

 # Note: assoc arrays will not be sorted, even if keys are numeric! join_arrays array1 array2 joined; eval merged+=(`dump_arrays joined`); declare -p merged 

declare -A merged = '([25] = "true" [20] = "true" ["30 30"] = "true" [10] = "true" [15] = "true" [0] = " 5 "[1] =" 10 "[2] =" 15 "[3] =" 20 "[4] =" 25 "[5] =" true30 30 ") '

Final note: above, you can see that the [5] key has the values ​​of the two keys of the original [5] arrays associated, because I used the + = operator. If you just use it to merge flag lists, it is safe, but to merge lists of significant values ​​with possible key conflicts, it is better to stick with the merge_array () function.

0
source

Source: https://habr.com/ru/post/985701/


All Articles