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


当前回答

我真的很喜欢Al P的答案,但想要唯一性强制廉价,所以我更进一步-使用目录。有一些明显的限制(目录文件限制,无效的文件名),但它应该适用于大多数情况。

hinit() {
    rm -rf /tmp/hashmap.$1
    mkdir -p /tmp/hashmap.$1
}

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

hget() {
    cat /tmp/hashmap.$1/$2
}

hkeys() {
    ls -1 /tmp/hashmap.$1
}

hdestroy() {
    rm -rf /tmp/hashmap.$1
}

hinit ids

for (( i = 0; i < 10000; i++ )); do
    hput ids "key$i" "value$i"
done

for (( i = 0; i < 10000; i++ )); do
    printf '%s\n' $(hget ids "key$i") > /dev/null
done

hdestroy ids

在我的测试中,它的表现也稍微好一点。

$ time bash hash.sh 
real    0m46.500s
user    0m16.767s
sys     0m51.473s

$ time bash dirhash.sh 
real    0m35.875s
user    0m8.002s
sys     0m24.666s

我只是想帮帮忙。干杯!

编辑:添加hdestroy()

其他回答

Bash 4

Bash 4本身支持这个特性。确保脚本的hashbang是#!/usr/bin/env bash或#!/bin/bash,这样你就不会使用sh。确保你要么直接执行你的脚本,要么用bash脚本执行脚本。(没有实际使用Bash执行Bash脚本的情况确实会发生,这将非常令人困惑!)

你可以这样声明一个关联数组:

declare -A animals

可以使用普通数组赋值操作符将其填充为元素。例如,如果你想要一个animal[sound(key)] = animal(value)的映射:

animals=( ["moo"]="cow" ["woof"]="dog")

或者在一行中声明和实例化:

declare -A animals=( ["moo"]="cow" ["woof"]="dog")

然后像普通数组一样使用它们。使用

Animals ['key']='value'设置值 ${animals[@]}展开值 “$ {!animals[@]}”(注意!)来展开键

别忘了引用他们的话:

echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done

Bash 3

在bash 4之前,没有关联数组。不要使用eval来模拟它们。像避免瘟疫一样避免eval,因为它是shell脚本的瘟疫。最重要的原因是eval将数据视为可执行代码(还有许多其他原因)。

首先也是最重要的:考虑升级到bash 4。这将使整个过程对你来说容易得多。

如果有不能升级的原因,声明是一个更安全的选择。它不像bash代码那样像eval那样计算数据,因此不允许如此容易地进行任意代码注入。

让我们通过介绍概念来准备答案:

首先,间接。

$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow

其次,声明:

$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow

把它们放在一起:

# Set a value:
declare "array_$index=$value"

# Get a value:
arrayGet() { 
    local array=$1 index=$2
    local i="${array}_$index"
    printf '%s' "${!i}"
}

让我们使用它:

$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow

注意:declare不能放在函数中。在bash函数中使用declare将其创建的变量转换为该函数范围内的局部变量,这意味着我们不能使用它访问或修改全局数组。(在bash 4中,您可以使用declare -g来声明全局变量——但在bash 4中,您可以首先使用关联数组,从而避免这种变通方法。)

简介:

升级到bash 4并对关联数组使用declare -A。 如果不能升级,请使用声明选项。 可以考虑使用awk,并完全避免这个问题。

这里有参数替换,尽管它也可能是非pc的…喜欢间接。

#!/bin/bash

# Array pretending to be a Pythonic dictionary
ARRAY=( "cow:moo"
        "dinosaur:roar"
        "bird:chirp"
        "bash:rock" )

for animal in "${ARRAY[@]}" ; do
    KEY="${animal%%:*}"
    VALUE="${animal##*:}"
    printf "%s likes to %s.\n" "$KEY" "$VALUE"
done

printf "%s is an extinct animal which likes to %s\n" "${ARRAY[1]%%:*}" "${ARRAY[1]##*:}"

BASH 4方式当然更好,但如果你需要一个hack…只有黑客才行。 您可以使用类似的技术搜索数组/散列。

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[@])

有两件事,在任何2.6内核中使用/dev/shm (Redhat)都可以使用内存而不是/tmp,其他发行版可能会有所不同。hget也可以用read重新实现,如下所示:

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来搜索最后一个条目:

握把/卒:

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

Read / echo:

$ time echo $(hget FD oracle)
3

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

在多次调用中,我从未看到过低于50%的改善。 这都是由于使用了/dev/shm.而导致的

我真的很喜欢Al P的答案,但想要唯一性强制廉价,所以我更进一步-使用目录。有一些明显的限制(目录文件限制,无效的文件名),但它应该适用于大多数情况。

hinit() {
    rm -rf /tmp/hashmap.$1
    mkdir -p /tmp/hashmap.$1
}

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

hget() {
    cat /tmp/hashmap.$1/$2
}

hkeys() {
    ls -1 /tmp/hashmap.$1
}

hdestroy() {
    rm -rf /tmp/hashmap.$1
}

hinit ids

for (( i = 0; i < 10000; i++ )); do
    hput ids "key$i" "value$i"
done

for (( i = 0; i < 10000; i++ )); do
    printf '%s\n' $(hget ids "key$i") > /dev/null
done

hdestroy ids

在我的测试中,它的表现也稍微好一点。

$ time bash hash.sh 
real    0m46.500s
user    0m16.767s
sys     0m51.473s

$ time bash dirhash.sh 
real    0m35.875s
user    0m8.002s
sys     0m24.666s

我只是想帮帮忙。干杯!

编辑:添加hdestroy()