如何在bash中组合关联数组



有人知道在bash中像普通数组一样组合两个关联数组的优雅方法吗?以下是我所说的:

在bash中,您可以将两个普通数组组合如下:

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

我想对两个关联数组做同样的事情,但以下代码不起作用:

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

它给出以下错误:

/associative_arrays.sh:第3行:array_bboth:true:分配关联数组时必须使用下标

以下是我想出的一个解决方案:

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

但我希望我实际上遗漏了一些语法,这些语法将允许如非工作示例中所示的一行赋值。

谢谢!

我也没有单行,但这里有一个不同的"解决方法",有些人可能喜欢使用字符串转换。这是4行,所以我离你想要的答案只有3个分号!

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

这适用于bash>4.3

print_associative_array() {
# declare a local **reference variable** (hence `-n`) named `array_reference`
# which is a reference to the value stored in the first parameter
# passed in
echo "printing associative array: $1"
local -n map_ref="$1"
# print the array by iterating through all of the keys now
for key in "${!map_ref[@]}"; do
value="${map_ref["$key"]}"
echo "  $key: $value"
done
}
merge_associative_array() {
# declare a local **reference variable** (hence `-n`) named `array_reference`
# which is a reference to the value stored in the first parameter
# passed in
echo "merging associative arrays: $1 <--- $2"
local -n map_ref="$1"
local -n map_ref2="$2"
# setting the value of keys in the second array, to the value of the same key in the first array
for key in "${!map_ref2[@]}"; do
value="${map_ref2["$key"]}"
echo "  $key: $value"
map_ref["$key"]="$value"
done
print_associative_array "$1"
}
declare -A optionsA
optionsA=( ["--hello"]="HELLO" ["--world"]="WORLD" )
declare -A optionsB
optionsB=( ["--key1"]="keyval" ["--world"]="WORLD2" ["--new-key"]="xyz" )
merge_associative_array "optionsA" "optionsB"

我被@Gabriel Staples 的回答所启发

#!/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

第二次尝试失败的主要原因是您试图使用相同的解决方案解决不同的问题。

在第一个数据集中,有两个数字索引数组,其中的键除了可能的出现顺序之外没有任何意义,它们的值才是真正重要的。我认为这意味着你想用一个新的索引将这些值线性连接到一个新数组中,这个索引会丢弃以前的键,但保持元素的原始顺序以及你传递它们的顺序。

第二个数据集有两个关联索引数组,其中键是值,值实际上只是占位符。我注意到您使用了数字键,如果您选择继续使用数字索引数组,则可以保留值的顺序和键的顺序,前提是您希望键按升序排列。。。

因此,为了解决这些问题,我编写了3个方便函数,它们使用declare和eval来加速连接/合并大型数组,而不是使用循环来分配每个数组。它们还采用可变数量的数组作为参数,这样您就可以随心所欲地加入/合并/转储它们。

注意:我将值/键"30"改为"30 30",以演示在某些情况下字符串的行为与数字不同。

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;

声明-ajoind='([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;

声明-ajoind='([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 joined='([0]="true"[1]="true"[2]="true"[3]="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 joined='([0]="true"[1]="true"[2]="true"[3]="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]="真"[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")

declare -p joined

declare-a joined='([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-合并后的="([25]="true"[20]="true"["30 30"]="true"[10]="真"[15]="真实"[0]="5"[1]="10"[2]="15"[3]="20"[4]="25"[5]="true30")">

最后一点注意:上面你可以看到Key[5]连接了两个源数组的Key[5]的值,因为我使用了+=运算符。如果您只是使用它来合并标志列表,这是安全的,但对于可能发生键冲突的有意义值的列表,最好使用merge_array()函数。

将'declare-p'的输出连接到数组中如何(毫无理由,它也不应该以'n'的方式工作,如图所示):

#! /bin/bash
declare -Ar array1=(  [5]=true [10]=true [15]=true )
declare -Ar array2=( [20]=true [25]=true [30]=true )
declare -Ar array3=( [35]=true [40]=true [45]=true )
# one liner:
eval declare -Ar array_both=($(declare -p array1 array2 array3 | sed -z -e $'s/declare[^(]*(//g' -e $'s/)[^ ]//g'))
# proof:
for k in ${!array_both[$*]} ; do
echo array_both[$k]=${array_both[$k}
done

尽管这个线程很旧,但我发现它是一个非常有用的问题,有着深刻的答案。这里有一个类似@Wil解释的方法。

与此方法类似,此方法不使用外部命令(如sed)。

主要区别在于它执行基于数组的合并,而不是基于字符串的合并。这允许以可预测的方式覆盖键值。它还支持将合并后的数组分配给只读变量,如问题中所示。

merge_map()
{
local -A merged_array
local array_string
while [ $# -gt 0 ]
do
array_string=$(declare -p $1)
eval merged_array+=${array_string#*=}
shift
done
array_string=$(declare -p merged_array)
echo "${array_string#*=}"
}
echo -e "nExample from question..."
# Values in posted question
declare -Ar array1=( [5]=true [10]=true [15]=true )
declare -Ar array2=( [20]=true [25]=true [30]=true )
eval declare -Ar array_both=$(merge_map array1 array2)
# Show result
for k in "${!array_both[@]}";{ echo "[$k]=${array_both[$k]}";}
echo -e "nExpanded example..."
# Non-numeric keys; some keys and values have spaces; more than two maps
declare -Ar expansion1=( [five]=true [ten]=true [and fifteen]=true )
declare -Ar expansion2=( [20]="true or false" [not 25]="neither true nor false" [30]=true )
declare -Ar expansion3=( [30]="was true, now false" [101]=puppies)
eval declare -Ar expansion_all=$(merge_map expansion1 expansion2 expansion3)
# Show result
for k in "${!expansion_all[@]}";{ echo "[$k]=${expansion_all[$k]}";}
eval "array_both=( ${array1[*]@K} ${array2[*]@K} )"

参见";"参数扩展";在man bash:中

${parameter@operator}
K      Produces a possibly-quoted version of the value of parameter,
except that it prints the values of indexed and associative arrays
as a sequence of quoted key-value pairs.

相关内容

  • 没有找到相关文章

最新更新