Shell脚本中的关联数组

Irf*_*qar 112 bash shell associative-array hashtable

我们需要一个模拟关联数组的脚本或类似于Shell Scripting的数据结构的Map,任何主体?

Bri*_*ell 143

如果可移植性不是您主要关注的另一个选择,则使用内置于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]}
Run Code Online (Sandbox Code Playgroud)

根据shell的不同,您可能需要typeset -A newmap代替declare -A newmap,或者在某些情况下可能根本不需要.

  • @Jer它很晦涩难懂,但要确定shell中是否设置了变量,可以使用`test -z $ {variable + x}`(```无关紧要,可以是任何字符串).对于Bash中的关联数组,你可以做类似的事情; 使用`test -z $ {map [key] + x}`. (2认同)

Bub*_*off 87

另一种非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"
Run Code Online (Sandbox Code Playgroud)

你也可以在那里抛出一个if语句进行搜索.if [[$ var =〜/ blah /]].管他呢.

  • 当您确实没有Bash 4时,此方法非常有用。但我认为以这种方式获取VALUE的行会更安全:VALUE = $ {animal#*:}。仅使用一个#字符,匹配将停止在第一个“:”上。这也允许值包含“:”。 (2认同)

Bri*_*ell 32

我认为你需要退一步思考一下地图或关联数组究竟是什么.它只是一种存储给定键值的方法,可以快速有效地恢复该值.您可能还希望能够遍历密钥以检索每个键值对,或删除键及其关联值.

现在,考虑一下您在shell脚本中一直使用的数据结构,甚至只是在没有编写脚本的shell中,它具有这些属性.难倒?这是文件系统.

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

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

如果您不想使用echocat,您可以随时写一些小包装; 这些是以Irfan为模型的,虽然它们只是输出值而不是设置任意变量,如$value:

#!/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
Run Code Online (Sandbox Code Playgroud)

编辑:这种方法实际上比使用提问者建议的sed的线性搜索快得多,而且更健壮(它允许键和值包含 - ,=,space,qnd":SP:").它使用文件系统的事实并没有使它变慢; 除非你打电话sync,否则这些文件实际上永远不会保证写入磁盘; 对于这样的临时文件,如果生命周期很短,那么它们中的许多文件永远不会被写入磁盘.

我使用以下驱动程序对Irfan的代码,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
Run Code Online (Sandbox Code Playgroud)

结果:

    $ 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

  • 这些文件不一定会被写入磁盘; 除非你调用同步,否则操作系统可能会将它们留在内存中.你的代码正在调用sed并进行几次线性搜索,这些搜索都非常慢.我做了一些快速基准测试,我的版本快了5-35倍. (8认同)
  • 无论如何,"快"和"贝壳"并没有真正结合在一起:当然不是因为我们在"避免微不足道的IO"级别上谈论的那种速度问题.您可以搜索并使用/ dev/shm来保证没有IO. (6认同)
  • 我认为你不应该为地图使用文件系统,基本上使用 IO 来做一些你可以在内存中相当快地完成的事情。 (3认同)
  • 这个解决方案让我感到惊讶,而且非常棒。在 2016 年仍然适用。它确实应该是公认的答案。 (3认同)

Jer*_*ner 20

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

get() {
    mapName=$1; key=$2

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
Run Code Online (Sandbox Code Playgroud)

  • 分叉子壳和sed几乎不是最佳的.Bash4原生支持这个,bash3有更好的选择. (15认同)

Dig*_*oss 15

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`
Run Code Online (Sandbox Code Playgroud)
$ sh hash.sh
Paris and Amsterdam and Madrid
Run Code Online (Sandbox Code Playgroud)


Wal*_*oss 8

另一种非 bash-4(即 bash 3,Mac 兼容)方式:

val_of_key() {
    case $1 in
        'A1') echo 'aaa';;
        'B2') echo 'bbb';;
        'C3') echo 'ccc';;
        *) echo 'zzz';;
    esac
}

for x in 'A1' 'B2' 'C3' 'D4'; do
    y=$(val_of_key "$x")
    echo "$x => $y"
done
Run Code Online (Sandbox Code Playgroud)

印刷:

A1 => aaa
B2 => bbb
C3 => ccc
D4 => zzz
Run Code Online (Sandbox Code Playgroud)

带有 的函数case就像一个关联数组。不幸的是它不能使用return,所以它必须echo输出,但这不是问题,除非你是一个避免分叉子 shell 的纯粹主义者。


lhu*_*ath 7

Bash4原生支持这一点.不要使用grepeval,他们是最丑陋的黑客.

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


小智 6

####################################################################
# 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; }'
}
Run Code Online (Sandbox Code Playgroud)

例:

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
Run Code Online (Sandbox Code Playgroud)


Llo*_*eki 5

对于 Bash 3,有一种特殊情况有一个很好且简单的解决方案:

如果您不想处理大量变量,或者键只是无效的变量标识符,并且您的数组保证少于 256 个项目,则可以滥用函数返回值。该解决方案不需要任何子 shell,因为该值可以随时作为变量使用,也不需要任何迭代,因此性能极高。而且它的可读性非常好,几乎就像 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[$?]}
Run Code Online (Sandbox Code Playgroud)

请记住,在 中使用单引号case,否则可能会出现通配符。从一开始对于静态/冻结哈希确实很有用,但是可以从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
Run Code Online (Sandbox Code Playgroud)

警告:长度现在不正确。

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

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[$?]}
Run Code Online (Sandbox Code Playgroud)

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

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))]}
Run Code Online (Sandbox Code Playgroud)