Shell脚本中的关联数组

我们需要一个脚本来模拟关联数组或像Shell数据结构的脚本,任何一个正文?

要添加到Irfan的答案 ,这里是get()的更短和更快的版本,因为它不需要迭代地图内容:

 get() { mapName=$1; key=$2 map=${!mapName} value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )" } 

如果可移植性不是您主要关心的另一选项,则使用内置于shell的关联数组。 这应该在bash 4.0(现在可以在大多数主要的发行版上find,尽pipe不是在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]} 

根据shell,你可能需要做一个typeset -A newmap而不是declare -A newmap ,或在一些可能根本没有必要。

另一个非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" 

你可以在那里抛出if语句来search。 如果[[$ var =〜/ blah /]]。 pipe他呢。

我认为你需要退后一步,想一想地图或关联数组的真实性。 所有这一切都是为一个给定的密钥存储一个值,并快速有效地返回该值。 您可能还希望能够迭代键来检索每个键值对,或者删除键及其相关值。

现在,考虑一下你在shell脚本中一直使用的数据结构,甚至在没有编写脚本的情况下,在shell中也有这些属性。 难倒? 这是文件系统。

真的,所有你需要在shell编程中有一个关联数组是一个临时目录。 mktemp -d是你的关联数组的构造函数:

 prefix=$(basename -- "$0") map=$(mktemp -dt ${prefix}) echo >${map}/key somevalue value=$(cat ${map}/key) 

如果你不喜欢使用echocat ,你总是可以写一些小包装; 这些模型是Irfan的模型,虽然他们只是输出值,而不是设置像$value这样的任意variables:

 #!/bin/sh prefix=$(basename -- "$0") mapdir=$(mktemp -dt ${prefix}) trap 'rm -r ${mapdir}' EXIT put() { [ "$#" != 3 ] && exit 1 mapname=$1; key=$2; value=$3 [ -d "${mapdir}/${mapname}" ] || mkdir "${mapdir}/${mapname}" echo $value >"${mapdir}/${mapname}/${key}" } get() { [ "$#" != 2 ] && exit 1 mapname=$1; key=$2 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 

编辑 :这种方法实际上比使用sedbuild议的线性search更快,而且更健壮(它允许键和值包含 – ,=,space,qnd“:SP:”)。 它使用文件系统的事实并没有让它变慢。 这些文件实际上不能保证写入磁盘,除非你调用sync ; 对于像这样短暂的临时文件,很可能永远不会写入磁盘。

我做了一些Irfan代码的基准testing,Jerry修改Irfan的代码和我的代码,使用下面的驱动程序:

 #!/bin/sh mapimpl=$1 numkeys=$2 numvals=$3 . ./${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 

结果:

     $ time ./driver.sh irfan 10 5

    实际0m0.975s
    用户0m0.280s
     sys 0m0.691s

     $ time ./driver.sh brian 10 5

    实际0m0.226s
    用户0m0.057s
     sys 0m0.123s

     $ time ./driver.sh jerry 10 5

    真正的0m0.706s
    用户0m0.228s
     sys 0m0.530s

     $ time ./driver.sh irfan 100 5

    真正的0m10.633s
    用户0m4.366s
     sys 0m7.127s

     $ time ./driver.sh brian 100 5

    真正0m1.682s
    用户0m0.546s
     sys 0m1.082s

     $ time ./driver.sh jerry 100 5

    实际0m9.315s
    用户0m4.565s
     sys 0m5.446s

     $ time ./driver.sh irfan 10 500

    真正的1m46.197s
    用户0m44.869s
     sys 1m12.282s

     $ time ./driver.sh brian 10 500

    实际0m16.003s
    用户0m5.135s
     sys 0m10.396s

     $ time ./driver.sh jerry 10 500

    真正的1m24.414s
    用户0m39.696s
     sys 0m54.834s

     $ time ./driver.sh irfan 1000 5

    真正的4m25.145s
    用户3m17.286s
     sys 1m21.490s

     $ time ./driver.sh brian 1000 5

    真实0分19.442秒
    用户0m5.287s
     sys 0m10.751s

     $ time ./driver.sh jerry 1000 5

    真正的5分29秒
    用户4m48.926s
     sys 0m59.336s

 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 
 #################################################################### # 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 "${1}$2"="$3" } # map_get map_name key # @return value # function map_get { alias "${1}$2" | awk -F"'" '{ print $2; }' } # map_keys map_name # @return map keys # function map_keys { alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }' } 

例:

 mapName=$(basename $0)_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 

Bash4本地支持这个。 不要使用grepeval ,他们是最丑陋的黑客。

有关示例代码的详细答案,请参阅: https : //stackoverflow.com/questions/3467959

现在回答这个问题。

以下脚本模拟shell脚本中的关联数组。 它简单,很容易理解。

映射只是一个永不结束的string,其keyValuePair保存为–name = Irfan –designation = SSE –company = My:SP:Own:SP:Company

空格replace为“:SP:”的值

 put() { if [ "$#" != 3 ]; then exit 1; fi mapName=$1; key=$2; value=`echo $3 | sed -e "s/ /:SP:/g"` eval map="\"\$$mapName\"" map="`echo "$map" | sed -e "s/--$key=[^ ]*//g"` --$key=$value" eval $mapName="\"$map\"" } get() { mapName=$1; key=$2; 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 

编辑:刚刚添加另一种方法来获取所有的密钥。

 getKeySet() { if [ "$#" != 1 ]; then exit 1; fi mapName=$1; eval map="\"\$$mapName\"" keySet=` echo $map | sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g" ` } 

对于Bash 3,有一个特别的例子,它有一个很好的简单的解决scheme:

如果你不想处理很多variables,或者键只是无效的variables标识符, 并且你的数组保证less于256个项目 ,你可以滥用函数返回值。 这个解决scheme不需要任何子shell,因为这个值是随时可用的variables,也没有任何迭代,所以性能尖叫。 它也是非常可读的,就像Bash 4版本一样。

这是最基本的版本:

 hash_index() { case $1 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[$?]} 

请记住,使用单引号的case ,否则它是受globbing。 从一开始就非常适用于静态/冻结散列,但是可以从hash_keys=()数组写入索引生成器。

注意,它默认是第一个,所以你可能要保留第零个元素:

 hash_index() { case $1 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 

警告:长度现在不正确。

或者,如果要保持从零开始的索引,则可以保留另一个索引值并防止不存在的密钥,但它的可读性较差:

 hash_index() { case $1 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[$?]} 

或者,为了保持长度正确,偏移索引为1:

 hash_index() { case $1 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))]} 

我已经发现,如前所述,最好的方法是将key / val写入文件,然后使用grep / awk来检索它们。 这听起来像是各种不必要的IO,但是磁盘caching起了作用,并且使其非常高效 – 比使用上述方法之一将内存存储在内存中要快得多(如基准testing所示)。

这是我喜欢的一个快速,干净的方法:

 hinit() { rm -f /tmp/hashmap.$1 } hput() { echo "$2 $3" >> /tmp/hashmap.$1 } hget() { grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };' } hinit capitols hput capitols France Paris hput capitols Netherlands Amsterdam hput capitols Spain Madrid echo `hget capitols France` and `hget capitols Netherlands` and `hget capitols Spain` 

如果你想强制每个键的单值,你也可以在hput()中做一些grep / sed操作。

真可惜我之前没有看到这个问题 – 我写了一个库shell框架 ,其中包含地图(关联数组)。 它的最后一个版本可以在这里find。

例:

 #!/bin/bash #include map library shF_PATH_TO_LIB="/usr/lib/shell-framework" source "${shF_PATH_TO_LIB}/map" #simple example get/put putMapValue "mapName" "mapKey1" "map Value 2" echo "mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")" #redefine old value to new putMapValue "mapName" "mapKey1" "map Value 1" echo "after change mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")" #add two new pairs key/values and print all keys putMapValue "mapName" "mapKey2" "map Value 2" putMapValue "mapName" "mapKey3" "map Value 3" echo -e "mapName keys are \n$(getMapKeys "mapName")" #create new map putMapValue "subMapName" "subMapKey1" "sub map Value 1" putMapValue "subMapName" "subMapKey2" "sub map Value 2" #and put it in mapName under key "mapKey4" putMapValue "mapName" "mapKey4" "subMapName" #check if under two key were placed maps echo "is map mapName[mapKey3]? - $(if isMap "$(getMapValue "mapName" "mapKey3")" ; then echo Yes; else echo No; fi)" echo "is map mapName[mapKey4]? - $(if isMap "$(getMapValue "mapName" "mapKey4")" ; then echo Yes; else echo No; fi)" #print map with sub maps printf "%s\n" "$(mapToString "mapName")" 

壳没有像数据结构的内置地图,我使用原始string来描述这样的项目:

 ARRAY=( "item_A|attr1|attr2|attr3" "item_B|attr1|attr2|attr3" "..." ) 

提取项目及其属性时:

 for item in "${ARRAY[@]}" do item_name=$(echo "${item}"|awk -F "|" '{print $1}') item_attr1=$(echo "${item}"|awk -F "|" '{print $2}') item_attr2=$(echo "${item}"|awk -F "|" '{print $3}') echo "${item_name}" echo "${item_attr1}" echo "${item_attr2}" done 

这似乎不像别人的答案那么聪明,而是让新人易于理解。

您可以使用dynamicvariables名称,并让variables名称像散列图的键一样工作。

例如,如果您有一个包含两列的input文件,名称,信用(例如下面的示例),并且您想对每个用户的收入进行求和:

 Mary 100 John 200 Mary 50 John 300 Paul 100 Paul 400 David 100 

下面的命令将以map _ $ {person}的forms使用dynamicvariables作为键来总和一切:

 while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log) 

阅读结果:

 set | grep map 

输出将是:

 map_David=100 map_John=500 map_Mary=150 map_Paul=500 

详细说明这些技术,我正在GitHub上开发一个像HashMap对象 shell_map一样的函数 。

为了创build“ HashMap实例 ”, shell_map函数可以使用不同的名字创build自己的副本。 每个新的函数副本将具有不同的$ FUNCNAMEvariables。 $ FUNCNAME然后用于为每个Map实例创build一个名称空间。

映射键是全局variables,格式为$ FUNCNAME_DATA_ $ KEY,其中$ KEY是添加到Map中的键。 这些variables是dynamicvariables 。

贝娄我会把它的简化版本,所以你可以用作例子。

 #!/bin/bash shell_map () { local METHOD="$1" case $METHOD in new) local NEW_MAP="$2" # 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/$2}" ;; put) local KEY="$2" local VALUE="$3" # declares a variable in the global scope eval ${FUNCNAME}_DATA_${KEY}='$VALUE' ;; get) local KEY="$2" local VALUE="${FUNCNAME}_DATA_${KEY}" echo "${!VALUE}" ;; keys) declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))" ;; name) echo $FUNCNAME ;; contains_key) local KEY="$2" 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="$2" unset ${FUNCNAME}_DATA_${KEY} ;; size) compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l ;; *) echo "unsupported operation '$1'." return 1 ;; esac } 

用法:

 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 "Mary" && echo "Mary has credit!" 

你没有具体说明你可以使用哪种shell语言,所以也许你可以考虑AWK,它内置了关联数组。

我用以下方法修改了Vadim的解决scheme:

 #################################################################### # 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 "${1}$2"="$3" } # map_get map_name key # @return value # function map_get { if type -p "${1}$2" then alias "${1}$2" | awk -F "'" '{ print $2; }'; fi } # map_keys map_name # @return map keys # function map_keys { alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }' } 

改变是为了防止它返回错误,以防止它返回错误,如果你请求一个不存在的关键,虽然副作用是它也将默默地忽略缺less的地图,但它适合我的用例更好,因为我只是想要检查一个密钥,以便跳过循环中的项目。

几年前,我为bash编写了脚本库,它支持关联数组和其他function(日志logging,configuration文件,对命令行参数的扩展支持,生成帮助,unit testing等)。 该库包含关联数组的包装,并自动切换到适当的模型(内部为bash4并为以前版本模拟)。 它被称为shell框架,并在origo.ethz.ch托pipe,但今天资源已closures。 如果有人仍然需要它,我可以与你分享。

晚回复,但考虑以这种方式解决问题,使用内置的bash内置读取,如下面的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