如何在Bash中定义哈希表?

825

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


6
让Bash运行Python/Perl脚本...这样非常灵活! - Déjà vu
1
参见:Shell脚本中的关联数组 - Gabriel Staples
15个回答

3

有两个事情可以做,你可以在任何 2.6 核心的系统中使用内存代替 /tmp 文件夹,只需使用 /dev/shm(适用于 Redhat 发行版),其他发行版可能有所不同。此外,可以按照以下方式使用 read 来重新实现 hget:

function hget {

  while read key idx
  do
    if [ $key = $2 ]
    then
      echo $idx
      return
    fi
  done < /dev/shm/hashmap.$1
}

此外,假设所有键都是唯一的,返回值会使读取循环短路,并避免不必要的读取。如果您的实现可以有重复的键,则只需省略返回值。这样可以节省读取和分叉grep和awk的开销。在两种实现中都使用/dev/shm,在搜索最后一个条目的3个条目哈希上使用time hget产生了以下结果:
Grep / Awk:
hget() {
    grep "^$2 " /dev/shm/hashmap.$1 | awk '{ print $2 };'
}

$ time echo $(hget FD oracle)
3

real    0m0.011s
user    0m0.002s
sys     0m0.013s

读取/回显:

$ time echo $(hget FD oracle)
3

real    0m0.004s
user    0m0.000s
sys     0m0.004s

在多次调用中,我从未见过少于50%的改进。这都归因于使用/dev/shm导致的分叉开销。


2
在bash 4之前,没有很好的方法在bash中使用关联数组。你最好使用一个实际支持这些东西的解释性语言,比如awk。另一方面,bash 4确实支持它们。
至于在bash 3中不太好的方法,这里有一个参考资料可能会有所帮助:http://mywiki.wooledge.org/BashFAQ/006

2

Bash 3解决方案:

在阅读了一些答案后,我组合了一个快速的小函数,希望能为其他人做出贡献。

# Define a hash like this
MYHASH=("firstName:Milan"
        "lastName:Adamovsky")

# Function to get value by key
getHashKey()
 {
  declare -a hash=("${!1}")
  local key
  local lookup=$2

  for key in "${hash[@]}" ; do
   KEY=${key%%:*}
   VALUE=${key#*:}
   if [[ $KEY == $lookup ]]
   then
    echo $VALUE
   fi
  done
 }

# Function to get a list of all keys
getHashKeys()
 {
  declare -a hash=("${!1}")
  local KEY
  local VALUE
  local key
  local lookup=$2

  for key in "${hash[@]}" ; do
   KEY=${key%%:*}
   VALUE=${key#*:}
   keys+="${KEY} "
  done

  echo $keys
 }

# Here we want to get the value of 'lastName'
echo $(getHashKey MYHASH[@] "lastName")


# Here we want to get all keys
echo $(getHashKeys MYHASH[@])

我认为这是一个相当不错的代码片段。它可能需要清理一下(但是不太需要)。在我的版本中,我将“key”重命名为“pair”,并将KEY和VALUE改为小写(因为当变量被导出时我使用大写)。我还将getHashKey重命名为getHashValue,并使键和值都为本地变量(尽管有时您可能不希望它们是局部的)。在getHashKeys中,我不给value赋任何值。我用分号进行分隔,因为我的值是URL。 - user1985657

0
这是一个相当牵强但希望有启发性的哈希/映射/字典/关联数组示例。假设我有一个字符串数组,并且我想创建一个从每个单词到其在数组中出现次数的映射。
当然,有一些使用管道命令来完成此操作的方法,但重点是说明核心映射操作:使用-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

0

我也使用了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

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