如何在bash中组合关联数组?

问题描述 投票:0回答:7

有谁知道在

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_both: 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

但我希望我实际上缺少一些允许单行赋值的语法,如非工作示例中所示。

谢谢!

bash associative-array
7个回答
3
投票

这适用于 bash > 4.3

我创建了我自己的函数

merge_associative_array
来做到这一点:

declare -A optionsA
optionsA=( ["--hello"]="HELLO" ["--world"]="WORLD" )
declare -A optionsB
optionsB=( ["--key1"]="keyval" ["--world"]="WORLD2" ["--new-key"]="xyz" )

merge_associative_array "optionsA" "optionsB"

这里是一个有用的数组打印机功能的实现:

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"
}

我受到@Gabriel Staples 的回答的启发


2
投票

我也没有单行代码,但这里有一种不同的“解决方法”,有人可能喜欢使用字符串转换。它有 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

1
投票

如何连接数组的“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



1
投票

虽然这个帖子已经很旧了,但我发现这是一个非常有帮助的问题,并且给出了富有洞察力的答案。这是与 @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]}";}


1
投票
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.

0
投票
#!/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
投票

您的第二次尝试不起作用的主要原因是您正在尝试使用相同的解决方案解决不同的问题。

在第一个数据集中,您有两个数字索引数组,其中键除了它们出现的顺序之外没有任何意义,而它们的值才是真正重要的。我的解释是,您希望将这些值线性连接到一个带有新索引的新数组,该索引会丢弃以前的键,但保持元素的原始顺序以及您传入它们的顺序。

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

因此,为了解决这些问题,我编写了 3 个便利函数,它们使用声明和 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;

声明 -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;

声明 -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;

声明 -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;

声明 -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;

声明 -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;

声明 -A merged='([25]="true" [20]="true" ["30 30"]="true" [10]="true" [15]="true" [5]= “真实”)'

# 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:表达式中的语法错误(错误标记为“30”)

declare -p joined

声明 -a join='([0]="5" [1]="10" [2]="15" [3]="20" [4]="25" [5]="30 30 “ [20] =“真” [25] =“真”)'

# 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

声明 -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”)'

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

© www.soinside.com 2019 - 2024. All rights reserved.