什么是Python字典的等效,但在Bash中(应该适用于OS X和Linux)。


当前回答

下面是一个相当做作但希望有指导意义的哈希/映射/字典/关联数组示例。假设我有一个字符串数组,我想创建一个映射,从每个单词到它在数组中出现的次数。

当然,有很多方法可以使用管道命令来实现这一点,但重点是演示核心的映射操作:使用-v检查键的存在性、添加键-值映射、检索键的值、更新键的现有值以及遍历整个映射以打印键-值对。

#!/usr/bin/bash
set -o pipefail

bash --version | head -1

words=(foo foo bar bar foo baz baz foo bar)
declare -A counter=() # create the map

for word in "${words[@]}"; do
    # if the key doesn't yet exist in the map, add it
    if [[ ! -v counter[$word] ]]; then
        counter[$word]=0
    fi

    # look up the value of a key, add one, and store back in the map
    counter[$word]=$((${counter[$word]} + 1))
done

# iterate the map
for key in "${!counter[@]}"; do
    echo "$key ${counter[$key]}"
done

输出:

GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
foo 4
bar 3
baz 2

其他回答

下面是一个相当做作但希望有指导意义的哈希/映射/字典/关联数组示例。假设我有一个字符串数组,我想创建一个映射,从每个单词到它在数组中出现的次数。

当然,有很多方法可以使用管道命令来实现这一点,但重点是演示核心的映射操作:使用-v检查键的存在性、添加键-值映射、检索键的值、更新键的现有值以及遍历整个映射以打印键-值对。

#!/usr/bin/bash
set -o pipefail

bash --version | head -1

words=(foo foo bar bar foo baz baz foo bar)
declare -A counter=() # create the map

for word in "${words[@]}"; do
    # if the key doesn't yet exist in the map, add it
    if [[ ! -v counter[$word] ]]; then
        counter[$word]=0
    fi

    # look up the value of a key, add one, and store back in the map
    counter[$word]=$((${counter[$word]} + 1))
done

# iterate the map
for key in "${!counter[@]}"; do
    echo "$key ${counter[$key]}"
done

输出:

GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
foo 4
bar 3
baz 2
hput () {
  eval hash"$1"='$2'
}

hget () {
  eval echo '${hash'"$1"'#hash}'
}
hput France Paris
hput Netherlands Amsterdam
hput Spain Madrid
echo `hget France` and `hget Netherlands` and `hget Spain`

$ sh hash.sh
Paris and Amsterdam and Madrid

考虑使用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

我也使用了bash4的方式,但我发现了一个恼人的bug。

我需要动态更新关联数组内容,所以我使用这种方式:

for instanceId in $instanceList
do
   aws cloudwatch describe-alarms --output json --alarm-name-prefix $instanceId| jq '.["MetricAlarms"][].StateValue'| xargs | grep -E 'ALARM|INSUFFICIENT_DATA'
   [ $? -eq 0 ] && statusCheck+=([$instanceId]="checkKO") || statusCheck+=([$instanceId]="allCheckOk"
done

我发现,与bash 4.3.11附加到字典中的现有键导致附加值,如果已经存在。例如,在一些重复之后,值的内容是“checkKOcheckKOallCheckOK”,这是不好的。

使用bash 4.3.39没有问题,其中附加一个存在的键意味着替换已经存在的实际值。

我解决了这个问题,只是在循环之前清洗/声明statusCheck关联数组:

unset statusCheck; declare -A statusCheck

你可以进一步修改hput()/hget()接口,这样你就有了如下命名的哈希值:

hput() {
    eval "$1""$2"='$3'
}

hget() {
    eval echo '${'"$1$2"'#hash}'
}

然后

hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`

这让你可以定义其他不冲突的地图(例如,'rcapitals'根据首都城市进行国家查找)。但是,不管怎样,我想你会发现这一切都很糟糕,就性能而言。

编辑:上面的修改版本,支持非字母数字字符的键

hashKey() {
  # replace non-alphanumeric characters with underscore to make keys valid BASH identifiers
  echo "$1_$2" | sed -E "s/[^a-zA-Z0-9]+/_/g" | sed -E "s/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+\$//g"
}

hashPut() {
  local KEY=`hashKey $1 $2`
  eval "$KEY"="$3"
}

hashGet() {
  local KEY=`hashKey $1 $2`
  echo "${!KEY}"
}

最后编辑

如果你真的想要快速哈希查找,有一个非常非常糟糕的黑客,它实际上非常有效。它是这样的:将您的键/值写入一个临时文件,每行一个,然后使用'grep "^$key"'将它们取出,使用带有cut或awk或sed或其他工具的管道来检索值。

就像我说的,这听起来很可怕,听起来它应该很慢,做各种不必要的IO,但实际上它非常快(磁盘缓存很棒,不是吗?),即使对于非常大的哈希表也是如此。你必须自己强制键的唯一性等等。即使只有几百个条目,输出文件/grep组合也会快很多——以我的经验,快几倍。它还消耗更少的内存。

这里有一种方法:

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

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

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

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

echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`