Shell脚本中的关联数组

154

我们需要一个能够模拟关联数组或类似于映射数据结构的脚本,用于Shell脚本编程。请问有人知道如何实现吗?


2
参见:如何在Bash中定义哈希表? - Gabriel Staples
17个回答

1
添加另一个选项,如果 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 '"' 

1
我发现正如前面提到的那样,最有效的方法是将键/值写入文件,然后使用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操作。

0

真遗憾我之前没有看到这个问题 - 我已经写了一个包含关联数组等内容的库shell-framework。你可以在这里找到它的最新版本。

例如:

#!/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 \n$(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\n" "$(mapToString "mapName")"

2
这些链接已经失效。 - Stuart R. Jefferys

0

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


1
可能值得把它放在 GitHub 上。 - Mark K Cowan

0

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

这个答案似乎不如其他人的聪明,但对于新手来说很容易理解。


-1

我对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函数时,防止由于请求一个不存在的键而返回错误。然而这样副作用是它将会忽略丢失的映射,并且并不会有任何提示,但是这更适合我的用例,因为我只是想检查一个键以便在循环中跳过某些项目。

-1
晚回复了,但考虑以这种方式解决问题,使用bash内置的read,如下面的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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接