【问题标题】:How to create a map of key:array in shell?如何在 shell 中创建 key:array 的映射?
【发布时间】:2017-05-31 06:04:42
【问题描述】:

我想在 shell 中创建地图。其中每个值都是一个数组。所以地图是键:数组对。例如它可以是这样的:

"Key1" : a1 a2 a3 a4
"key2" : b1 b2 b3
"key3" : c1

基本上我的代码是这样的

listService(){
serviceType=$1
servicesList=($(getServices $serviceType))
}

listService serviceTypeA
listService serviceTypeB
listService serviceTypeC

这里的getServices 是一个函数,它根据作为$serviceType 传递的参数返回一个服务数组。所以每次我调用listService 函数时,我的serviceList 都会被新的服务列表覆盖。但我想以这样的地图形式保留来自不同服务类型的所有服务:

"serviceA" : a1 a2 a3 a4
"serviceB" : b1 b2 b3
"serviceC" : c1

之后我想根据键访问每个数组。如何实现这一点。

提前感谢您的帮助。

编辑:我尝试了@cdarke 提供的答案。这是我现在的代码:

#!/bin/bash
declare -A arrayMap

getValues(){
  key=$1
  case $key in
    AAA )
    arr=( AA AAA AAAA )
      ;;
    BBB )
    arr=( BB BB BBBB )
      ;;
    CCC )
    arr=()
    ;;
    esac
    echo "${arr[@]}"
}

fillArrayMap(){
  param=$1
  values=( $(getValues $param) )
  printf "\nIn $param\n"
  echo -e "\nArray values is: ${values[@]}\n"
  printf "\nLength of the array values is : ${#values[@]}\n"
  arrayMap["$param"]=$values #THIS IS THE KEY LINE
  valuesList=${arrayMap[$param]} 
  echo -e "\nArray valuesList is: ${valuesList[@]}\n"
  printf "\nLength of the array valuesList is : ${#valuesList[@]}\n"
}

fillArrayMap AAA
fillArrayMap BBB
fillArrayMap CCC

现在从输出中我可以看到valuesList 仅获取values 数组的第一个元素。但我希望valuesList 包含方法getValues 返回的所有元素。即

valuesList= ${arrayMap[$param]}

现在valuesList 应该包含所有元素,而现在它只包含 1 个元素。如何解决?

注意:我的目标是访问每个单独的元素,如 AAA 或 AA,我不需要像 AA AAA AAAA 这样的字符串作为一个整体

【问题讨论】:

  • gawk 有二维数组,(实际上是由 hastable 实现的),如果可以拖入其他逻辑,可以考虑使用 awk。
  • 或者在 python 中完成你的工作。 ;-)

标签: arrays bash shell unix


【解决方案1】:

Bash 不支持多维数组,但我认为您不需要。您可以将字符串以列表的形式存储在数组元素中,这将为您提供所需的内容。

# My made-up version of getServices
getServices() {
    nm="$1"
    last=${nm##*Type}
    retn=(${last}1 ${last}2 ${last}3 ${last}4)
    echo "${retn[@]}"
}


declare -A serviceList
listService(){
    serviceType="$1"

    # Here I use the key to make an assignment, which adds to the hash
    serviceList["$serviceType"]=$(getServices $serviceType) 
}

listService serviceTypeA
listService serviceTypeB
listService serviceTypeC

for key in ${!serviceList[@]}
do
    echo "\"$key\": ${serviceList[$key]}"
done

给予:

"serviceTypeC": C1 C2 C3 C4
"serviceTypeB": B1 B2 B3 B4
"serviceTypeA": A1 A2 A3 A4

编辑新问题:

改变:

arrayMap["$param"]=$values     # THIS IS THE KEY LINE
valuesList=${arrayMap[$param]} 

到:

arrayMap["$param"]=${values[@]} 
valuesList=( ${arrayMap[$param]}  )  

当您仅通过名称 ($values) 引用数组变量时,您只会得到第一个元素

【讨论】:

  • 感谢您的回答。我尝试并遇到了另一个问题。在问题详细信息中添加了相同的内容。你能建议什么@cdarke吗?
【解决方案2】:

正如 cdarke 已经提到的,bash 数组是一维的。多年来,人们想出了“伪造”多维数组的方法。

我使用的两种方法是维护数组描述数组,或指向其他数组的指针数组。我会用前者回答;如果您想自己探索,后者应该是显而易见的。

这是一个用于填充变量的数组内容的最小示例:

#!/usr/bin/env bash

declare -A a=(
  [b]='([0]="one" [1]="two")'
  [c]='([0]="three" [1]="four")'
)

declare -p a

for key in ${!a[@]}; do
  declare -a $key="${a[$key]}"
  declare -p $key
done

生产:

declare -A a=([b]="([0]=\"one\" [1]=\"two\")" [c]="([0]=\"three\" [1]=\"four\")" )
declare -a b=([0]="one" [1]="two")
declare -a c=([0]="three" [1]="four")

这里的关键是你使用declare 来引用$key 的值,因为你不能在bash 中只说$var="value"

当然,如果您不想为$key 的值命名变量,则无需这样做。将值存储在 $value 中,可以让您在 $key 中使用特殊字符。

如果它不会冒犯您的敏感性或过多地限制您的键名,一个更简单的替代方法是将declare -p 命令的整个输出存储在数组的值中,然后将eval 它存储在你需要它。例如:

declare -A a=(
 [b]='declare -a b=([0]="one" [1]="two")'
 [c]='declare -a c=([0]="three" [1]="four")'
)

for key in ${!a[@]}; do
  eval "${a[$key]}"
done

Some people don't like eval. :-) 它仍然存在,但是在您的工具箱中。

就您而言,这有点难以提供建议,因为您没有提供完整的 MCVE,但这是我人为的示例。

#!/usr/bin/env bash

# contrived getServices function, since you didn't provide one
getServices() {
    local -a value=()
    local last="${1:$((${#1}-1)):1}"   # last character of $1
    for n in $( seq 1 $(( $RANDOM / 8192 + 1 )) ); do
      value+=(${last}${n})
    done
    declare -p value     # output of this function is actual bash code.
}

# populate the array
listService() {
    servicesList[$1]=$( getServices $1 )
}

# Initialize this as empty to make `eval` safer
declare -A servicesList=()

# These services seem interesting.
listService serviceA
listService serviceB
listService serviceC

# Note that we're stepping through KEYS here, not values.
for row in "${!servicesList[@]}"; do
    printf '"%s": ' "$row"
    eval "${servicesList[$row]}"   # Someone is bound to complain about this.
    for column in "${!value[@]}"; do
        # Add whatever $row and $column specific code you like here.
        printf '%s ' "${value[$column]}"
    done
    printf "\n"
done

我的输出:

$ bash 2dimarrayexample
"serviceC": C1
"serviceB": B1 B2 B3 B4
"serviceA": A1 A2

当然,您的输出可能会有所不同,因为 getServices 会产生随机输出。 :)

【讨论】:

  • 为什么输出的顺序与您运行listService 的顺序不同?另外,以这种方式使用eval 有什么风险?一定很微妙,我根本看不到。
  • @Graham - 关联数组在 bash 中没有按顺序排列。如果您需要保持顺序,您可以维护一个单独的非关联(编号)数组,指向关联数组中的键,然后使用 for 循环遍历第二个数组以保证您的顺序。
猜你喜欢
  • 1970-01-01
  • 2012-11-15
  • 2012-03-29
  • 2023-04-08
  • 2011-10-13
  • 2019-11-20
  • 2013-11-19
  • 2019-12-14
  • 1970-01-01
相关资源
最近更新 更多