How to define hash tables in Bash?
什么是相似的Python字典,但在Bash中(应该适用于OS X和Linux)。
Bash 4
Bash 4原生支持此功能。确保您的脚本的hashbang是
通过执行以下操作声明关联数组:
1 | declare -A animals |
您可以使用常规数组赋值运算符填充元素。例如,如果您想要一张
1 | animals=( ["moo"]="cow" ["woof"]="dog") |
或合并它们:
1 | declare -A animals=( ["moo"]="cow" ["woof"]="dog") |
然后像普通数组一样使用它们。使用
1 2 | echo"${animals[moo]}" for sound in"${!animals[@]}"; do echo"$sound - ${animals[$sound]}"; done |
Bash 3
在bash 4之前,你没有关联数组。不要使用
首先:考虑升级到bash 4.这将使整个过程更加轻松。
如果您无法升级,
让我们通过介绍概念来准备答案:
首先,间接。
1 2 | $ animals_moo=cow; sound=moo; i="animals_$sound"; echo"${!i}" cow |
其次,
1 2 | $ sound=moo; animal=cow; declare"animals_$sound=$animal"; echo"$animals_moo" cow |
将他们聚集在一起:
1 2 3 4 5 6 7 8 9 | # Set a value: declare"array_$index=$value" # Get a value: arrayGet() { local array=$1 index=$2 local i="${array}_$index" printf '%s'"${!i}" } |
我们来使用它:
1 2 3 4 5 | $ sound=moo $ animal=cow $ declare"animals_$sound=$animal" $ arrayGet animals"$sound" cow |
注意:
摘要:
-
升级到bash 4并使用
declare -A 进行关联数组。 -
如果无法升级,请使用
declare 选项。 -
请考虑使用
awk ,并完全避免此问题。
有参数替换,虽然它也可能是非PC的......就像间接一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #!/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. ""$KEY""$VALUE" done printf"%s is an extinct animal which likes to %s ""${ARRAY[1]%%:*}""${ARRAY[1]##*:}" |
BASH 4的方式当然更好,但是如果你需要一个黑客......只有一个黑客会做。
您可以使用类似的技术搜索数组/哈希。
这就是我在这里寻找的:
1 2 3 4 5 6 7 | declare -A hashmap hashmap["key"]="value" hashmap["key2"]="value2" echo"${hashmap["key"]}" for key in ${!hashmap[@]}; do echo $key; done for value in ${hashmap[@]}; do echo $value; done echo hashmap has ${#hashmap[@]} elements |
这对bash 4.1.5不起作用:
1 | animals=( ["moo"]="cow" ) |
您可以进一步修改hput()/ hget()接口,以便命名哈希,如下所示:
1 2 3 4 5 6 7 | hput() { eval"$1""$2"='$3' } hget() { eval echo '${'"$1$2"'#hash}' } |
然后
1 2 3 4 | 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")。但是,无论哪种方式,我认为你会发现这一切都非常糟糕,性能明智。
如果你真的想要快速哈希查找,那么一个可怕的,可怕的黑客实际上工作得非常好。就是这样:将你的键/值写入临时文件,每行一个,然后使用'grep"^ $ key"'将它们取出,使用带有cut或awk或sed的管道或其他任何方法来检索值。
就像我说的,听起来很糟糕,听起来它应该很慢并且做各种不必要的IO,但实际上它非常快(磁盘缓存很棒,不是吗?),即使对于非常大的哈希表。您必须自己强制执行密钥唯一性等。即使您只有几百个条目,输出文件/ grep组合也会快得多 - 根据我的经验,速度要快几倍。它也减少了记忆。
这是一种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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` |
只需使用文件系统
文件系统是可以用作哈希映射的树结构。
您的哈希表将是一个临时目录,您的密钥将是文件名,您的值将是文件内容。 优点是它可以处理巨大的哈希映射,并且不需要特定的shell。
哈希表创作
添加元素
读一个元素
性能
当然,它的速度慢,但不是那么慢。
我在我的机器上测试了它,带有SSD和btrfs,每秒大约有3000个元素读/写。
1 2 3 4 5 6 7 8 9 10 11 | 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` |
1 2 | $ sh hash.sh Paris and Amsterdam and Madrid |
考虑使用bash内置读取的解决方案,如下面的ufw防火墙脚本的代码片段所示。该方法具有使用尽可能多的定界字段集(不仅仅是2)的优点。我们用过|分隔符,因为端口范围说明符可能需要冒号,即6001:6010。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #!/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 |
我同意@lhunath和其他人认为关联数组是Bash 4的方法。如果你坚持使用Bash 3(OSX,你无法更新的旧发行版),你可以使用expr,它应该是无处不在的,一个字符串和正则表达式。我喜欢它,特别是当字典不是太大时。
将地图写为字符串(注意分隔符','也在开头和结尾)
1 | animals=",moo:cow,woof:dog," |
使用正则表达式提取值
1 2 3 | get_animal { echo"$(expr"$animals" :".*,$1:\([^,]*\),.*")" } |
拆分字符串以列出项目
1 2 3 4 5 6 7 8 9 10 | get_animal_items { arr=$(echo"${animals:1:${#animals}-2}" | tr","" ") for i in $arr do value="${i##*:}" key="${i%%:*}" echo"${value} likes to $key" done } |
现在你可以使用它:
1 2 3 4 5 | $ animal = get_animal"moo" cow $ get_animal_items cow likes to moo dog likes to woof |
我真的很喜欢Al P的答案,但希望廉价执行的独特性,所以我更进一步 - 使用目录。有一些明显的限制(目录文件限制,文件名无效)但它应该适用于大多数情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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 ' $(hget ids"key$i") > /dev/null done hdestroy ids |
它在我的测试中也表现得更好一些。
1 2 3 4 5 6 7 8 9 | $ 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 3解决方案:
在阅读一些答案时,我把一个快速的小函数放在一起,我想回馈一下,这可能对其他人有帮助。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | # 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[@]) |
一位同事刚提到这个帖子。我在bash中独立实现了哈希表,并且它不依赖于版本4.我在2010年3月的一篇博客文章中(在此之前的一些答案......之前)在bash中标题为哈希表:
我之前使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 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是快速的一次性,这样的事情应该很少涉及可能需要哈希的复杂性,除了你的
有两件事,你可以在任何内核2.6中使用内存代替/ tmp,使用/ dev / shm(Redhat)其他发行版可能会有所不同。另外hget可以使用如下读取重新实现:
1 2 3 4 5 6 7 8 9 10 11 | 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条哈希搜索最后一个条目时使用时间hget产生以下内容:
grep的/ awk中:
1 2 3 4 5 6 7 8 9 10 | 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 |
读/回声:
1 2 3 4 5 6 | $ time echo $(hget FD oracle) 3 real 0m0.004s user 0m0.000s sys 0m0.004s |
在多次调用中,我从未见过少于50%的改进。
由于使用
在bash 4之前,没有好的方法在bash中使用关联数组。你最好的选择是使用一种实际上支持这类东西的解释语言,比如awk。另一方面,bash 4确实支持它们。
至于bash 3中不太好的方法,这里有一个参考而不是可能有帮助:http://mywiki.wooledge.org/BashFAQ/006
我也使用了bash4方式,但我找到了烦人的bug。
我需要动态更新关联数组内容,所以我用这种方式:
1 2 3 4 5 | 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附加到dict中的现有键会导致附加值(如果已经存在)。因此,例如在一些重复之后,值的内容是"checkKOcheckKOallCheckOK",这并不好。
bash 4.3.39没有问题,其中附加现有密钥意味着如果已经存在,则替代该实际值。
我解决了这个问题,只是在cicle之前清理/声明了statusCheck关联数组:
1 | unset statusCheck; declare -A statusCheck |
我使用动态变量在bash 3中创建HashMaps。 我在以下答案中解释了它是如何工作的:Shell脚本中的关联数组
您还可以查看shell_map,它是在bash 3中实现的HashMap实现。