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


当前回答

有两件事,在任何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.而导致的

其他回答

只使用文件系统:文件系统是一个树结构,可以用作散列映射。 您的哈希表将是一个临时目录,键将是文件名,值将是文件内容。它的优点是可以处理巨大的hashmap,并且不需要特定的shell。

哈希表创建

hashtable = $ (mktemp - d)

添加一个元素

Echo $value > "$hashtable/$key"

读取一个元素

价值= $ (< hashtable美元- key美元”)

性能

当然,它很慢,但也没那么慢。 我在我的机器上测试了它,使用SSD和btrfs,它每秒可以读/写大约3000个元素。

我真的很喜欢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中没有使用关联数组的好方法。最好的办法是使用一种真正支持这些功能的解释语言,比如awk。另一方面,bash 4确实支持它们。

至于bash 3中不太好的方法,这里有一个参考:http://mywiki.wooledge.org/BashFAQ/006

一位同事刚刚提到了这个帖子。我在bash中独立实现了哈希表,它不依赖于版本4。我在2010年3月的一篇博客文章(在这里的一些答案之前…)题为bash中的哈希表:

我以前使用cksum来散列,但后来将Java的字符串hashCode转换为本地bash/zsh。

# Here's the hashing function
ht() {
  local h=0 i
  for (( i=0; i < ${#1}; i++ )); do
    let "h=( (h<<5) - h ) + $(printf %d \'${1:$i:1})"
    let "h |= h"
  done
  printf "$h"
}

# Example:

myhash[`ht foo bar`]="a value"
myhash[`ht baz baf`]="b value"

echo ${myhash[`ht baz baf`]} # "b value"
echo ${myhash[@]} # "a value b value" though perhaps reversed
echo ${#myhash[@]} # "2" - there are two values (note, zsh doesn't count right)

它不是双向的,内置的方式要好得多,但无论如何都不应该使用。Bash是用于快速一次性的,这类事情很少涉及可能需要散列的复杂性,除非在~/中。Bashrc和朋友们。

你可以进一步修改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`