131

連想配列をシミュレートするスクリプト、またはシェル スクリプト用のデータ構造のようなマップが必要でした。

4

17 に答える 17

108

別の非 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 ステートメントをスローすることもできます。[[ $var =~ /blah/ ]] の場合。または何でも。

于 2010-12-14T22:14:23.170 に答える
35

一歩下がって、マップ、または連想配列が実際に何であるかを考える必要があると思います。これは、特定のキーの値を格納し、その値を迅速かつ効率的に取得する方法にすぎません。キーを反復処理してすべてのキーと値のペアを取得したり、キーとそれに関連付けられた値を削除したりすることもできます。

ここで、シェル スクリプトで常に使用するデータ構造について考えてみましょう。また、スクリプトを記述せずにシェルだけで使用する場合でも、これらのプロパティを持つデータ構造について考えてみてください。困った?それはファイルシステムです。

実際、シェル プログラミングで連想配列を使用するために必要なのは一時ディレクトリだけです。mktemp -dあなたの連想配列コンストラクターです:

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

echoandを使いたくない場合はcat、いつでも小さなラッパーを書くことができます。これらは 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

edit : このアプローチは、質問者が提案した sed を使用した線形検索よりも実際にはかなり高速であり、より堅牢です (キーと値に -、=、スペース、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

結果:

    $ time ./driver.sh irfan 10 5

    実質 0m0.975s
    ユーザー 0分0.280秒
    システム 0m0.691s

    $ time ./driver.sh ブライアン 10 5

    実質 0m0.226s
    ユーザー 0m0.057s
    システム 0m0.123s

    $ time ./driver.sh ジェリー 10 5

    実質 0m0.706s
    ユーザー 0分0.228秒
    システム 0m0.530s

    $ time ./driver.sh irfan 100 5

    実質 0m10.633s
    ユーザー 0m4.366s
    システム 0 分 7.127 秒

    $ time ./driver.sh ブライアン 100 5

    実質 0m1.682s
    ユーザー 0m0.546s
    システム 0m1.082s

    $ time ./driver.sh ジェリー 100 5

    実質 0 分 9.315 秒
    ユーザー 0m4.565s
    システム 0 分 5.446 秒

    $ time ./driver.sh irfan 10 500

    実質 1 分 46.197 秒
    ユーザー 0分44.869秒
    システム 1m12.282s

    $ time ./driver.sh ブライアン 10 500

    実質 0m16.003s
    ユーザー 0m5.135s
    システム 0m10.396s

    $ time ./driver.sh jerry 10 500

    実質 1m24.414s
    ユーザー 0 分 39.696 秒
    システム 0m54.834s

    $ time ./driver.sh irfan 1000 5

    実質 4m25.145s
    ユーザー 3m17.286s
    sys 1m21.490s

    $ time ./driver.sh ブライアン 1000 5

    実 0m19.442s
    ユーザー 0m5.287s
    システム 0m10.751s

    $ time ./driver.sh ジェリー 1000 5

    リアル 5m29.136s
    ユーザー 4m48.926s
    システム 0 分 59.336 秒

于 2009-03-27T18:46:41.680 に答える
21

Irfan の回答に追加するにはget()、マップの内容を反復する必要がないため、短くて高速なバージョンを次に示します。

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

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
于 2009-03-27T16:48:49.060 に答える
7

Bash4はこれをネイティブにサポートします。grepまたはを使用しないでくださいeval。これらは最も醜いハックです。

サンプルコードを含む詳細で詳細な回答については、 https ://stackoverflow.com/questions/3467959を参照してください。

于 2010-08-12T13:20:22.217 に答える
7
####################################################################
# 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
于 2011-06-28T15:28:24.810 に答える
4

Bash 3 の場合、適切でシンプルな解決策を持つ特定のケースがあります。

多くの変数を処理したくない場合、またはキーが単に無効な変数識別子であり、配列の項目数が 256 未満であることが保証されている場合は、関数の戻り値を悪用できます。このソリューションは、値が変数としてすぐに利用できるため、サブシェルを必要とせず、パフォーマンスが悲鳴を上げるような反復も必要ありません。また、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。そうしないと、グロビングの対象になります。最初から静的/凍結されたハッシュに非常に役立ちますが、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))]}
于 2014-03-03T16:15:01.647 に答える
4

今、この質問に答えています。

次のスクリプトは、シェル スクリプトで連想配列をシミュレートします。シンプルでとても理解しやすいです。

Map は --name=Irfan --designation=SSE --company=My:SP:Own:SP:Company として保存された keyValuePair を持つ終わりのない文字列に他なりません

値のスペースは「: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"
          `
}
于 2009-03-27T14:07:49.057 に答える
1

jq が利用可能な場合、別のオプションを追加します。

export NAMES="{
  \"Mary\":\"100\",
  \"John\":\"200\",
  \"Mary\":\"50\",
  \"John\":\"300\",
  \"Paul\":\"100\",
  \"Paul\":\"400\",
  \"David\":\"100\"
}"
export NAME=David
echo $NAMES | jq --arg v "$NAME" '.[$v]' | tr -d '"' 
于 2018-07-13T20:11:25.583 に答える
0

残念なことに、私は以前に質問を見ませんでした - 私は、とりわけマップ(連想配列)を含むライブラリシェルフレームワークを書きました。最後のバージョンはここにあります。

例:

#!/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")"
于 2011-03-01T21:29:49.363 に答える
0

数年前、他の機能 (ロギング、構成ファイル、コマンドライン引数の拡張サポート、ヘルプの生成、単体テストなど) の中で連想配列をサポートする bash 用のスクリプト ライブラリを作成しました。ライブラリには連想配列のラッパーが含まれており、適切なモデルに自動的に切り替わります (bash4 の内部および以前のバージョンのエミュレート)。shell-framework と呼ばれ、origo.ethz.ch でホストされていましたが、今日、リソースは閉鎖されています。それでも必要な人がいれば、あなたと共有できます。

于 2013-06-13T12:38:12.123 に答える
0

すでに述べたように、最も効果的な方法は、キー/値をファイルに書き出してから、grep/awk を使用してそれらを取得することです。あらゆる種類の不必要な IO のように聞こえますが、ディスク キャッシュが有効になり、非常に効率的になります。上記の方法のいずれかを使用してメモリに格納するよりもはるかに高速です (ベンチマークが示すように)。

私が気に入っている簡単でクリーンな方法は次のとおりです。

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 アクションを実行することもできます。

于 2010-02-09T17:19:59.610 に答える
-1

Vadim のソリューションを次のように変更しました。

####################################################################
# 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; }'
}

変更は、存在しないキーを要求した場合にエラーが返されないようにするための map_get への変更ですが、副作用として、欠落しているマップも黙って無視しますが、私のユースケースにより適しています。ループ内のアイテムをスキップするためにキーをチェックしたかった。

于 2013-04-11T14:22:16.080 に答える