shell脚本中,无法获取“关联数组”(字典)的key,尝试过很多方法,输出都不对,求教

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shell脚本中,无法获取“关联数组”(字典)的key,尝试过很多方法,输出都不对,求教相关的知识,希望对你有一定的参考价值。

shell脚本如下:
#!/bin/bash
declare -A phone
phone=(["key1"]=135 ["key2"]=136 ["key3"]=158)

echo $!phone[*]
echo $!phone[@]
#########################
运行结果如下:
~ ./shell.sh
./shell.sh: line 2: declare: -A: invalid option
declare: usage: declare [-afFirtx] [-p] [name[=value] ...]
0
0
去掉数组声明运行结果如下:
0
0

求教各位,网上几乎所有例子都是$!phone[*]或者$!phone[@] 这两种方法,但是我确实取不出来呀,求教!

    看下你使用的bash版本到没到4.0,使用指令bash --version查看

    使用变量前先确认该变量是否在之前已经被定义过了 使用source 命令或  . 命令启动脚本时是不会开辟新线程的所以你在bash窗口里定义的没加local 变量 都会被脚本继承 此时你可以先删除这个变量 即定义phone 时 unset phone

参考技术A

你去掉数组声明的话就不能声明是关联数组了

用下面的试下看看

declare -A phone1
phone1=(["key1"]=135 ["key2"]=136 ["key3"]=158)
echo "$phone1[key1]"
# 如果报 -bash: declare: phone: cannot convert indexed to associative array 就换个变量名

参考技术B 关联数组,是bash 4.0新增的特性,你的shell版本多少 参考技术C sh -x yourshell
看看问题在哪

Shell脚本中的关联数组

我们需要一个模拟关联数组的脚本或类似于Shell Scripting的数据结构的Map,任何主体?

答案

要添加到Irfan's answer,这里有一个更短更快的get()版本,因为它不需要迭代地图内容:

get() {
    mapName=$1; key=$2

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=([^ ]*).*/1/" -e 's/:SP:/ /g' )"
}
另一答案

您可以使用动态变量名称,并让变量名称像hashmap的键一样工作。

例如,如果您有一个包含两列的输入文件,name,credit,如下例所示,并且您希望将每个用户的收入相加:

Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100

以下命令将使用动态变量作为键对所有内容求和,以map _ $ {person}的形式:

while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)

要阅读结果:

set | grep map

输出将是:

map_David=100
map_John=500
map_Mary=150
map_Paul=500

详细阐述这些技术,我正在GitHub上开发一个像HashMap对象shell_map一样工作的函数。

为了创建“HashMap实例”,shell_map函数能够以不同的名称创建自己的副本。每个新函数副本都有一个不同的$ FUNCNAME变量。 $ FUNCNAME然后用于为每个Map实例创建命名空间。

映射键是全局变量,形式为$ FUNCNAME_DATA_ $ KEY,其中$ KEY是添加到Map的键。这些变量是dynamic variables

Bellow我会给它一个简化版本,以便您可以使用它作为示例。

#!/bin/bash

shell_map () {
    local METHOD="$1"

    case $METHOD in
    new)
        local NEW_MAP="$2"

        # loads shell_map function declaration
        test -n "$(declare -f shell_map)" || return

        # declares in the Global Scope a copy of shell_map, under a new name.
        eval "${_/shell_map/$2}"
    ;;
    put)
        local KEY="$2"  
        local VALUE="$3"

        # declares a variable in the global scope
        eval ${FUNCNAME}_DATA_${KEY}='$VALUE'
    ;;
    get)
        local KEY="$2"
        local VALUE="${FUNCNAME}_DATA_${KEY}"
        echo "${!VALUE}"
    ;;
    keys)
        declare | grep -Po "(?<=${FUNCNAME}_DATA_)w+((?==))"
    ;;
    name)
        echo $FUNCNAME
    ;;
    contains_key)
        local KEY="$2"
        compgen -v ${FUNCNAME}_DATA_${KEY} > /dev/null && return 0 || return 1
    ;;
    clear_all)
        while read var; do  
            unset $var
        done < <(compgen -v ${FUNCNAME}_DATA_)
    ;;
    remove)
        local KEY="$2"
        unset ${FUNCNAME}_DATA_${KEY}
    ;;
    size)
        compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l
    ;;
    *)
        echo "unsupported operation '$1'."
        return 1
    ;;
    esac
}

用法:

shell_map new credit
credit put Mary 100
credit put John 200
for customer in `credit keys`; do 
    value=`credit get $customer`       
    echo "customer $customer has $value"
done
credit contains_key "Mary" && echo "Mary has credit!"
另一答案

遗憾的是我之前没有看到这个问题 - 我已经写了库shell-framework,其中包含地图(关联数组)。它的最后一个版本可以找到here

例:

#!/bin/bash 
#include map library
shF_PATH_TO_LIB="/usr/lib/shell-framework"
source "${shF_PATH_TO_LIB}/map"

#simple example get/put
putMapValue "mapName" "mapKey1" "map Value 2"
echo "mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"

#redefine old value to new
putMapValue "mapName" "mapKey1" "map Value 1"
echo "after change mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"

#add two new pairs key/values and print all keys
putMapValue "mapName" "mapKey2" "map Value 2"
putMapValue "mapName" "mapKey3" "map Value 3"
echo -e "mapName keys are 
$(getMapKeys "mapName")"

#create new map
putMapValue "subMapName" "subMapKey1" "sub map Value 1"
putMapValue "subMapName" "subMapKey2" "sub map Value 2"

#and put it in mapName under key "mapKey4"
putMapValue "mapName" "mapKey4" "subMapName"

#check if under two key were placed maps
echo "is map mapName[mapKey3]? - $(if isMap "$(getMapValue "mapName" "mapKey3")" ; then echo Yes; else echo No; fi)"
echo "is map mapName[mapKey4]? - $(if isMap "$(getMapValue "mapName" "mapKey4")" ; then echo Yes; else echo No; fi)"

#print map with sub maps
printf "%s
" "$(mapToString "mapName")"
另一答案

我已经发现,正如已经提到的那样,最好的方法是将键/值写出来,然后使用grep / awk来检索它们。这听起来像各种不必要的IO,但磁盘缓存启动并使其非常高效 - 比使用上述方法之一(如基准测试显示)将它们存储在内存中要快得多。

这是一个我喜欢的快速,干净的方法:

hinit() {
    rm -f /tmp/hashmap.$1
}

hput() {
    echo "$2 $3" >> /tmp/hashmap.$1
}

hget() {
    grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };'
}

hinit capitols
hput capitols France Paris
hput capitols Netherlands Amsterdam
hput capitols Spain Madrid

echo `hget capitols France` and `hget capitols Netherlands` and `hget capitols Spain`

如果你想强制每个键的单值,你也可以在hput()中做一些grep / sed动作。

另一答案

几年前我为bash编写了脚本库,它支持关联数组和其他功能(日志记录,配置文件,对命令行参数的扩展支持,生成帮助,单元测试等)。该库包含关联数组的包装器,并自动切换到适当的模型(内部用于bash4,模拟用于以前的版本)。它被称为shell-framework并在origo.ethz.ch上托管,但今天资源已关闭。如果有人仍然需要它,我可以与你分享。

另一答案

Shell没有像数据结构那样的内置映射,我使用原始字符串来描述这样的项目:

ARRAY=(
    "item_A|attr1|attr2|attr3"
    "item_B|attr1|attr2|attr3"
    "..."
)

提取项目及其属性时:

for item in "${ARRAY[@]}"
do
    item_name=$(echo "${item}"|awk -F "|" '{print $1}')
    item_attr1=$(echo "${item}"|awk -F "|" '{print $2}')
    item_attr2=$(echo "${item}"|awk -F "|" '{print $3}')

    echo "${item_name}"
    echo "${item_attr1}"
    echo "${item_attr2}"
done

这似乎并不比其他人的答案聪明,但容易理解为新人们的外壳。

另一答案

如果jq可用,则添加另一个选项:

export NAMES="{
  "Mary":"100",
  "John":"200",
  "Mary":"50",
  "John":"300",
  "Paul":"100",
  "Paul":"400",
  "David":"100"
}"
export NAME=David
echo $NAMES | jq --arg v "$NAME" '.[$v]' | tr -d '"' 
另一答案

我用以下方法修改了Vadim的解决方案:

####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
    alias "${1}$2"="$3"
}

# map_get map_name key
# @return value
#
function map_get {
    if type -p "${1}$2"
        then
            alias "${1}$2" | awk -F "'" '{ print $2; }';
    fi
}

# map_keys map_name 
# @return map keys
#
function map_keys
{
    alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}

更改是map_get,以防止它在请求不存在的密钥时返回错误,虽然副作用是它也会默默地忽略丢失的映射,但它更适合我的用例,因为我只是想要检查一个键,以便跳过循环中的项目。

另一答案

迟到的回复,但考虑以这种方式解决问题,使用bash内置读取,如下面的ufw防火墙脚本的代码片段中所示。该方法具有使用尽可能多的定界字段集(不仅仅是2)的优点。我们用过|分隔符,因为端口范围说明符可能需要冒号,即6001:6010。

#!/usr/bin/env bash

readonly connections=(       
                            '192.168.1.4/24|tcp|22'
                            '192.168.1.4/24|tcp|53'
                            '192.168.1.4/24|tcp|80'
                            '192.168.1.4/24|tcp|139'
                            '192.168.1.4/24|tcp|443'
                            '192.168.1.4/24|tcp|445'
                            '192.168.1.4/24|tcp|631'
                            '192.168.1.4/24|tcp|5901'
                            '192.168.1.4/24|tcp|6566'
)

function set_connections(){
    local range proto port
    for fields in ${connections[@]}
    do
            IFS=$'|' read -r range proto port <<< "$fields"
            ufw allow from "$range" proto "$proto" to any port "$port"
    done
}

set_connections
另一答案

如果可移植性不是您主要关注的另一个选择,则使

以上是关于shell脚本中,无法获取“关联数组”(字典)的key,尝试过很多方法,输出都不对,求教的主要内容,如果未能解决你的问题,请参考以下文章

shell脚本进阶之数组基础

Shell脚本中的关联数组

shell 边边角角

shell 脚本-关联数组

Shell脚本入门 03:字符串与数组

shell脚本命令