bash Shell 脚本中的关联数组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/688849/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Associative arrays in Shell scripts
提问by Irfan Zulfiqar
We required a script that simulates Associative arrays or Map like data structure for Shell Scripting, any body?
我们需要一个脚本来模拟关联数组或类似映射的 Shell 脚本数据结构,任何主体?
采纳答案by Jerry Penner
To add to Irfan's answer, here is a shorter and faster version of get()
since it requires no iteration over the map contents:
要添加到Irfan 的答案,这里有一个更短更快的版本,get()
因为它不需要对地图内容进行迭代:
get() {
mapName=; key=
map=${!mapName}
value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*//" -e 's/:SP:/ /g' )"
}
回答by Brian Campbell
Another option, if portability is not your main concern, is to use associative arrays that are built in to the shell. This should work in bash 4.0 (available now on most major distros, though not on OS X unless you install it yourself), ksh, and zsh:
如果可移植性不是您主要关心的问题,另一种选择是使用内置于 shell 的关联数组。这应该适用于 bash 4.0(现在可在大多数主要发行版上使用,但除非您自己安装,否则不适用于 OS X)、ksh 和 zsh:
declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"
echo ${newmap[company]}
echo ${newmap[name]}
Depending on the shell, you may need to do a typeset -A newmap
instead of declare -A newmap
, or in some it may not be necessary at all.
根据外壳程序,您可能需要执行一个typeset -A newmap
而不是declare -A newmap
,或者在某些情况下可能根本不需要。
回答by Bubnoff
Another non-bash 4 way.
另一种非 bash 4 方式。
#!/bin/bash
# A pretend Python dictionary with bash 3
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
echo -e "${ARRAY[1]%%:*} is an extinct animal which likes to ${ARRAY[1]#*:}\n"
You could throw an if statement for searching in there as well. if [[ $var =~ /blah/ ]]. or whatever.
你也可以抛出一个 if 语句在那里搜索。如果 [[ $var =~ /blah/ ]]。管他呢。
回答by Brian Campbell
I think that you need to step back and think about what a map, or associative array, really is. All it is is a way to store a value for a given key, and get that value back quickly and efficiently. You may also want to be able to iterate over the keys to retrieve every key value pair, or delete keys and their associated values.
我认为您需要退后一步思考映射或关联数组到底是什么。它只是一种为给定键存储值并快速有效地取回该值的方法。您可能还希望能够遍历键以检索每个键值对,或删除键及其关联的值。
Now, think about a data structure you use all the time in shell scripting, and even just in the shell without writing a script, that has these properties. Stumped? It's the filesystem.
现在,考虑一下您在 shell 脚本中一直使用的数据结构,甚至只是在不编写脚本的情况下在 shell 中使用的具有这些属性的数据结构。难倒?是文件系统。
Really, all you need to have an associative array in shell programming is a temp directory. mktemp -d
is your associative array constructor:
实际上,在 shell 编程中你需要一个关联数组就是一个临时目录。mktemp -d
是你的关联数组构造函数:
prefix=$(basename -- "#!/bin/sh
prefix=$(basename -- "#!/bin/sh
mapimpl=
numkeys=
numvals=
. ./${mapimpl}.sh #/ <- fix broken stack overflow syntax highlighting
for (( i = 0 ; $i < $numkeys ; i += 1 ))
do
for (( j = 0 ; $j < $numvals ; j += 1 ))
do
put "newMap" "key$i" "value$j"
get "newMap" "key$i"
done
done
")
mapdir=$(mktemp -dt ${prefix})
trap 'rm -r ${mapdir}' EXIT
put() {
[ "$#" != 3 ] && exit 1
mapname=; key=; value=
[ -d "${mapdir}/${mapname}" ] || mkdir "${mapdir}/${mapname}"
echo $value >"${mapdir}/${mapname}/${key}"
}
get() {
[ "$#" != 2 ] && exit 1
mapname=; key=
cat "${mapdir}/${mapname}/${key}"
}
put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"
value=$(get "newMap" "company")
echo $value
value=$(get "newMap" "name")
echo $value
")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)
If you don't feel like using echo
and cat
, you can always write some little wrappers; these ones are modelled off of Irfan's, though they just output the value rather than setting arbitrary variables like $value
:
如果您不想使用echo
and cat
,您可以随时编写一些小包装器;这些是模仿 Irfan 的,尽管它们只是输出值而不是设置任意变量,例如$value
:
$ time ./driver.sh irfan 10 5 real 0m0.975s user 0m0.280s sys 0m0.691s $ time ./driver.sh brian 10 5 real 0m0.226s user 0m0.057s sys 0m0.123s $ time ./driver.sh jerry 10 5 real 0m0.706s user 0m0.228s sys 0m0.530s $ time ./driver.sh irfan 100 5 real 0m10.633s user 0m4.366s sys 0m7.127s $ time ./driver.sh brian 100 5 real 0m1.682s user 0m0.546s sys 0m1.082s $ time ./driver.sh jerry 100 5 real 0m9.315s user 0m4.565s sys 0m5.446s $ time ./driver.sh irfan 10 500 real 1m46.197s user 0m44.869s sys 1m12.282s $ time ./driver.sh brian 10 500 real 0m16.003s user 0m5.135s sys 0m10.396s $ time ./driver.sh jerry 10 500 real 1m24.414s user 0m39.696s sys 0m54.834s $ time ./driver.sh irfan 1000 5 real 4m25.145s user 3m17.286s sys 1m21.490s $ time ./driver.sh brian 1000 5 real 0m19.442s user 0m5.287s sys 0m10.751s $ time ./driver.sh jerry 1000 5 real 5m29.136s user 4m48.926s sys 0m59.336s
edit: This approach is actually quite a bit faster than the linear search using sed suggested by the questioner, as well as more robust (it allows keys and values to contain -, =, space, qnd ":SP:"). The fact that it uses the filesystem does not make it slow; these files are actually never guaranteed to be written to the disk unless you call sync
; for temporary files like this with a short lifetime, it's not unlikely that many of them will never be written to disk.
编辑:这种方法实际上比提问者建议的使用 sed 的线性搜索快得多,而且更健壮(它允许键和值包含 -、=、空格、qnd“:SP:”)。它使用文件系统的事实并没有使它变慢。除非您调用sync
,否则这些文件实际上永远不会被保证写入磁盘;对于像这样生命周期很短的临时文件,它们中的许多永远不会被写入磁盘是不太可能的。
I did a few benchmarks of Irfan's code, Jerry's modification of Irfan's code, and my code, using the following driver program:
我使用以下驱动程序对 Irfan 的代码、Jerry 对 Irfan 代码的修改以及我的代码进行了一些基准测试:
hput () {
eval hash""=''
}
hget () {
eval echo '${hash'""'#hash}'
}
hput France Paris
hput Netherlands Amsterdam
hput Spain Madrid
echo `hget France` and `hget Netherlands` and `hget Spain`
The results:
结果:
$ sh hash.sh
Paris and Amsterdam and Madrid
回答by DigitalRoss
####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
alias ""=""
}
# map_get map_name key
# @return value
#
function map_get
{
alias "" | awk -F"'" '{ print ; }'
}
# map_keys map_name
# @return map keys
#
function map_keys
{
alias -p | grep | cut -d'=' -f1 | awk -F"" '{print ; }'
}
mapName=$(basename put() {
if [ "$#" != 3 ]; then exit 1; fi
mapName=; key=; value=`echo | sed -e "s/ /:SP:/g"`
eval map="\"$$mapName\""
map="`echo "$map" | sed -e "s/--$key=[^ ]*//g"` --$key=$value"
eval $mapName="\"$map\""
}
get() {
mapName=; key=; valueFound="false"
eval map=$$mapName
for keyValuePair in ${map};
do
case "$keyValuePair" in
--$key=*) value=`echo "$keyValuePair" | sed -e 's/^[^=]*=//'`
valueFound="true"
esac
if [ "$valueFound" == "true" ]; then break; fi
done
value=`echo $value | sed -e "s/:SP:/ /g"`
}
put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"
get "newMap" "company"
echo $value
get "newMap" "name"
echo $value
)_map_
map_put $mapName "name" "Irfan Zulfiqar"
map_put $mapName "designation" "SSE"
for key in $(map_keys $mapName)
do
echo "$key = $(map_get $mapName $key)
done
回答by lhunath
Bash4 supports this natively. Do not use grep
or eval
, they are the ugliest of hacks.
Bash4 本身就支持这一点。不要使用grep
or eval
,它们是最丑陋的黑客。
For a verbose, detailed answer with example code see: https://stackoverflow.com/questions/3467959
有关示例代码的详细答案,请参阅:https: //stackoverflow.com/questions/3467959
回答by Vadim
getKeySet() {
if [ "$#" != 1 ];
then
exit 1;
fi
mapName=;
eval map="\"$$mapName\""
keySet=`
echo $map |
sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--//g"
`
}
Example:
例子:
hash_index() {
case in
'foo') return 0;;
'bar') return 1;;
'baz') return 2;;
esac
}
hash_vals=("foo_val"
"bar_val"
"baz_val");
hash_index "foo"
echo ${hash_vals[$?]}
回答by Irfan Zulfiqar
Now answering this question.
现在回答这个问题。
Following scripts simulates associative arrays in shell scripts. Its simple and very easy to understand.
以下脚本模拟 shell 脚本中的关联数组。它简单易懂。
Map is nothing but a never ending string that has keyValuePair saved as --name=Irfan --designation=SSE --company=My:SP:Own:SP:Company
Map 只不过是一个永无止境的字符串,将 keyValuePair 保存为 --name=Irfan --designation=SSE --company=My:SP:Own:SP:Company
spaces are replaced with ':SP:' for values
空格替换为 ':SP:' 值
hash_index() {
case in
'foo') return 1;;
'bar') return 2;;
'baz') return 3;;
esac
}
hash_vals=("", # sort of like returning null/nil for a non existent key
"foo_val"
"bar_val"
"baz_val");
hash_index "foo" || echo ${hash_vals[$?]} # It can't get more readable than this
edit:Just added another method to fetch all keys.
编辑:刚刚添加了另一种方法来获取所有键。
hash_index() {
case in
'foo') return 0;;
'bar') return 1;;
'baz') return 2;;
*) return 255;;
esac
}
hash_vals=("foo_val"
"bar_val"
"baz_val");
hash_index "foo"
[[ $? -ne 255 ]] && echo ${hash_vals[$?]}
回答by Lloeki
For Bash 3, there is a particular case that has a nice and simple solution:
对于 Bash 3,有一个特殊情况有一个很好且简单的解决方案:
If you don't want to handle a lot of variables, or keys are simply invalid variable identifiers, andyour array is guaranteed to have less than 256 items, you can abuse function return values. This solution does not require any subshell as the value is readily available as a variable, nor any iteration so that performance screams. Also it's very readable, almost like the Bash 4 version.
如果您不想处理大量变量,或者键只是无效的变量标识符,并且您的数组保证少于 256 项,则可以滥用函数返回值。此解决方案不需要任何子 shell,因为该值可以作为变量随时可用,也不需要任何迭代,因此性能会大打折扣。它也非常易读,几乎就像 Bash 4 版本。
Here's the most basic version:
这是最基本的版本:
hash_index() {
case in
'foo') return 1;;
'bar') return 2;;
'baz') return 3;;
esac
}
hash_vals=("foo_val"
"bar_val"
"baz_val");
hash_index "foo" || echo ${hash_vals[$(($? - 1))]}
Remember, use single quotes in case
, else it's subject to globbing. Really useful for static/frozen hashes from the start, but one could write an index generator from a hash_keys=()
array.
请记住,在 中使用单引号case
,否则会受到通配符的影响。从一开始就对静态/冻结哈希非常有用,但可以从hash_keys=()
数组中编写索引生成器。
Watch out, it defaults to the first one, so you may want to set aside zeroth element:
注意,它默认为第一个,因此您可能需要留出第零个元素:
Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100
Caveat: the length is now incorrect.
警告:长度现在不正确。
Alternatively, if you want to keep zero-based indexing, you can reserve another index value and guard against a non-existent key, but it's less readable:
或者,如果您想保留从零开始的索引,您可以保留另一个索引值并防范不存在的键,但它的可读性较差:
while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)
Or, to keep the length correct, offset index by one:
或者,为了保持长度正确,将索引偏移一:
set | grep map
回答by Bruno Negr?o Zica
You can use dynamic variable names and let the variables names work like the keys of a hashmap.
您可以使用动态变量名称并让变量名称像哈希图的键一样工作。
For example, if you have an input file with two columns, name, credit, as the example bellow, and you want to sum the income of each user:
例如,如果您有一个输入文件有两列,名称,信用,如下例所示,并且您想对每个用户的收入求和:
map_David=100
map_John=500
map_Mary=150
map_Paul=500
The command bellow will sum everything, using dynamic variables as keys, in the form of map_${person}:
下面的命令将使用动态变量作为键,以map_${person}的形式汇总所有内容:
#!/bin/bash
shell_map () {
local METHOD=""
case $METHOD in
new)
local NEW_MAP=""
# loads shell_map function declaration
test -n "$(declare -f shell_map)" || return
# declares in the Global Scope a copy of shell_map, under a new name.
eval "${_/shell_map/}"
;;
put)
local KEY=""
local VALUE=""
# declares a variable in the global scope
eval ${FUNCNAME}_DATA_${KEY}='$VALUE'
;;
get)
local KEY=""
local VALUE="${FUNCNAME}_DATA_${KEY}"
echo "${!VALUE}"
;;
keys)
declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))"
;;
name)
echo $FUNCNAME
;;
contains_key)
local KEY=""
compgen -v ${FUNCNAME}_DATA_${KEY} > /dev/null && return 0 || return 1
;;
clear_all)
while read var; do
unset $var
done < <(compgen -v ${FUNCNAME}_DATA_)
;;
remove)
local KEY=""
unset ${FUNCNAME}_DATA_${KEY}
;;
size)
compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l
;;
*)
echo "unsupported operation ''."
return 1
;;
esac
}
To read the results:
读取结果:
shell_map new credit
credit put Mary 100
credit put John 200
for customer in `credit keys`; do
value=`credit get $customer`
echo "customer $customer has $value"
done
credit contains_key "Mary" && echo "Mary has credit!"
The output will be:
输出将是:
##代码##Elaborating on these techniques, I'm developing on GitHub a function that works just like a HashMap Object, shell_map.
在阐述这些技术,我在GitHub上开发的作品就像一个功能HashMap对象,shell_map。
In order to create "HashMap instances" the shell_map functionis able create copies of itself under different names. Each new function copy will have a different $FUNCNAME variable. $FUNCNAME then is used to create a namespace for each Map instance.
为了创建“ HashMap 实例”,shell_map 函数能够以不同的名称创建其自身的副本。每个新的函数副本都有一个不同的 $FUNCNAME 变量。$FUNCNAME 然后用于为每个 Map 实例创建一个命名空间。
The map keys are global variables, in the form $FUNCNAME_DATA_$KEY, where $KEY is the key added to the Map. These variables are dynamic variables.
映射键是全局变量,格式为 $FUNCNAME_DATA_$KEY,其中 $KEY 是添加到映射中的键。这些变量是动态变量。
Bellow I'll put a simplified version of it so you can use as example.
波纹管我会放一个简化版本,以便您可以用作示例。
##代码##Usage:
用法:
##代码##