等同于Python dictionaries,但在Bash中(应该适用于OS X和Linux)。
答案 0 :(得分:784)
Bash 4原生支持此功能。确保您的脚本的hashbang为#!/usr/bin/env bash
或#!/bin/bash
,因此您最终不会使用sh
。确保您要么直接执行脚本,要么使用script
执行bash script
。 (实际上没有执行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 4之前,你没有关联数组。 请勿使用eval
来模拟。避免像瘟疫一样eval
,因为 是shell脚本的瘟疫。最重要的原因是eval
将您的数据视为可执行代码(还有许多其他原因)。
首先:考虑升级到bash 4.这将使整个过程更加轻松。
如果您无法升级,declare
是一个更安全的选择。它不像eval
那样将数据评估为bash代码,因此不容易任意代码注入。
让我们通过介绍概念来准备答案:
首先,间接。
$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow
其次,declare
:
$ 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
会将它创建的变量 local 转换为该函数的范围,这意味着我们无法使用它来访问或修改全局数组。 (在bash 4中,你可以使用declare -g来声明全局变量 - 但是在bash 4中,你可以首先使用关联数组,避免这种解决方法。)
要点:
declare -A
作为关联数组。declare
选项。awk
代替完全避免此问题。答案 1 :(得分:104)
有参数替换,但它也可能是非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方式当然更好,但是如果你需要一个黑客......只有一个黑客会做。 您可以使用类似的技术搜索数组/哈希。
答案 2 :(得分:64)
这就是我在这里寻找的:
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:
animals=( ["moo"]="cow" )
答案 3 :(得分:23)
您可以进一步修改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',其按国家/地区查找国家/地区)。但是,无论哪种方式,我认为你会发现这一切都非常糟糕,性能明智。
如果你真的想要快速哈希查找,那么一个可怕的,可怕的黑客实际上非常有效。就是这样:将你的键/值写入临时文件,每行一个,然后使用'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`
答案 4 :(得分:15)
文件系统是一种可用作哈希映射的树结构。 您的哈希表将是一个临时目录,您的密钥将是文件名,您的值将是文件内容。优点是它可以处理巨大的哈希图,并且不需要特定的shell。
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
当然,它的速度慢,但 慢。 我在我的机器上使用SSD和btrfs进行了测试,并且每秒 3000元素读/写。
答案 5 :(得分:14)
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
答案 6 :(得分:10)
考虑使用bash builtin 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
答案 7 :(得分:6)
我同意@lhunath和其他人认为关联数组是Bash 4的方法。如果你坚持使用Bash 3(OSX,你无法更新的旧发行版),你可以使用expr,它应该是无处不在,一个字符串和正则表达式。我喜欢它,特别是当字典不是太大时。
将地图写为字符串(请注意分隔符&#39;,&#39;也在开头和结尾)
animals=",moo:cow,woof:dog,"
使用正则表达式提取值
get_animal {
echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
}
拆分字符串以列出项目
get_animal_items {
arr=$(echo "${animals:1:${#animals}-2}" | tr "," "\n")
for i in $arr
do
value="${i##*:}"
key="${i%%:*}"
echo "${value} likes to $key"
done
}
现在你可以使用它了:
$ animal = get_animal "moo"
cow
$ get_animal_items
cow likes to moo
dog likes to woof
答案 8 :(得分:5)
我真的很喜欢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()
答案 9 :(得分:2)
在bash 4之前,没有好的方法在bash中使用关联数组。你最好的选择是使用一种实际上支持这类东西的解释语言,比如awk。另一方面,bash 4 支持它们。
对于bash 3中 less 的好方法,这里有一个可能有帮助的参考:http://mywiki.wooledge.org/BashFAQ/006
答案 10 :(得分:2)
有两件事,你可以在任何内核2.6中使用内存代替/ tmp,使用/ dev / shm(Redhat)其他发行版可能会有所不同。另外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条哈希搜索最后一个条目时使用时间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
的使用,这可以归结为叉头。
答案 11 :(得分:2)
一位同事刚提到这个帖子。我已经在bash中独立实现了哈希表,并且它不依赖于版本4.来自我的2010年3月的博客文章(在此处的一些答案之前......),标题为Hash tables in bash:
我previously使用cksum
进行哈希,但后来将Java's string 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
和朋友之外。
答案 12 :(得分: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[@])
答案 13 :(得分: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附加到dict中的现有密钥会导致附加值(如果已存在)。因此,例如在一些重复之后,值的内容是“checkKOcheckKOallCheckOK”,这不是很好。
bash 4.3.39没有问题,其中附加现有密钥意味着如果已经存在则取代该实际值。
我解决了这个问题,只是在cicle:
之前清理/声明了statusCheck关联数组unset statusCheck; declare -A statusCheck
答案 14 :(得分:0)
我使用动态变量在bash 3中创建HashMaps。我在答案中解释了它是如何工作的:Associative arrays in Shell scripts
另外,您可以查看 shell_map ,这是在bash 3中实现的HashMap实现。