質問

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

役に立ちましたか?

解決

それはマップの内容に対して何ら反復を必要としないので、ここでget()の短縮と高速バージョンである、イルファンの答えをhref="https://stackoverflow.com/a/689890">

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

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

他のヒント

移植性は、あなたの主な関心事でない場合は、

別のオプションは、シェルに組み込まれた連想配列を使用することです。 (あなたがそれを自分でインストールしない限りはないがOS X上、ほとんどの主要なディストリビューションで使用できるようになりました)。これは、bashの4.0で動作するはずです、kshの、およびzshのます:

declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"

echo ${newmap[company]}
echo ${newmap[name]}

シェルに応じて、あなたの代わりにtypeset -A newmapdeclare -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文を投げることができました。 [[$ VAR =〜/何とか/]]の場合。 またはどんなます。

私は、あなたが一歩、マップ、または連想配列は、本当に何であるかについて考える必要があると思います。それはすべてが戻って迅速かつ効率的にその値を指定されたキーの値を格納し、取得する方法です。あなたはまた、すべてのキーと値のペアを取得、またはキーとその値を削除するには、キーを反復処理することができるようにしたいことがあります。

さて、これらの性質を持っている、あなたは、スクリプトを記述することなく、シェルスクリプトでは、とだけでも、シェルですべての時間を使用したデータ構造を考えます。困惑?それはファイルシステムです。

本当に、あなたはシェルプログラミングで連想配列を持っている必要があり、すべては一時ディレクトリです。 mktemp -dがあなたの連想配列のコンストラクタです。

prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)
あなたはechocatを使用してのように感じていない場合は、

、あなたは常にいくつかの小さなラッパーを書くことができます。これらのものは、イルファンさんのオフにモデル化されている彼らだけ値の出力ではなく、$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

編集:このアプローチはかなり速く、質問者によって提案されたsedのと同様に、より堅牢な(それはキーと値を含めることができます使用して線形検索よりも実際にある - 、=、スペースをQND ":SP:")。それはファイルシステムを使用するという事実は、それが遅くなりません。あなたがsyncを呼び出さない限り、これらのファイルは、実際にディスクに書き込まれることが保証されることはありません。短い寿命を持つこのような一時ファイルのために、それはそれらの多くがディスクに書き込まれないことをそうではありません。

私は、次のドライバプログラムを使用して、イルファンのコード、イルファンのコードのジェリーの変更、そして私のコードのいくつかのベンチマークを行いました

#!/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

    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

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

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

のコード例を参照してくださいと冗長、詳細な回答の場合: https://stackoverflow.com/questions/3467959

####################################################################
# 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

さて、この質問に答えるます。

次のスクリプトは、シェルスクリプトで連想配列をシミュレートします。そのシンプルで理解することは非常に簡単ます。

地図はkeyValuePairとして保存されている決して終わることのない文字列以外の何ものでもありません --name =イルファン--designation = SSE --company =マイ:SP:自身:SP:会社

スペースが置き換えられます ':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"
          `
}

バッシュ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に単一引用符を使用します。本当に最初から静的/凍結されたハッシュのための便利な、しかし、1つは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[$?]}

あるいは、正しい長さを保つために、一つのオフセットインデックスます:

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))]}

動的変数名を使用して、変数名をハッシュマップのキーのように機能させることができます。

たとえば、次の例のように、名前、クレジットという 2 つの列を含む入力ファイルがあり、各ユーザーの収入を合計したいとします。

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

以下のコマンドは、動的変数をキーとして使用して、次の形式ですべてを合計します。 地図_${人}:

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 インスタンスシェルマップ関数 は、別の名前で自分自身のコピーを作成できます。新しい関数のコピーにはそれぞれ異なる $FUNCNAME 変数が含まれます。次に、$FUNCNAME を使用して、各 Map インスタンスの名前空間を作成します。

マップ キーは、$FUNCNAME_DATA_$KEY 形式のグローバル変数です。ここで、$KEY はマップに追加されるキーです。これらの変数は、 動的変数.

以下に、例として使用できるように、簡略化したバージョンを示します。

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

私は最善の実施方法は、ファイルにキー/ヴァルスを書き出し、その後、それらを取得するためには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のアクションを行うことができます()。

数年前、私は他の機能(ログ、設定ファイル、コマンドライン引数の拡張サポート、ヘルプを生成し、ユニットテストなど)の中で連想配列をサポートbashのためのスクリプトライブラリを書きました。ライブラリは、連想配列のラッパーが含まれており、自動的に適切なモデルに切り替える(bash4ための内部および以前のバージョンのためにエミュレートします)。これは、シェル・フレームワークと呼ばれ、origo.ethz.chでホストされているが、今日は、リソースがクローズされました。誰かがまだそれを必要とする場合、私はあなたとそれを共有することができます。

シェルは、データ構造のような組み込みのマップを持っていない、私はそのような項目を記述するために、生の文字列を使用します:

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

これは、他の人の答えよりも賢いが、シェルに新しい人々のために理解することは容易ではないように思える。

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 '"' 

私は次のようにヴァディムのソリューションを変更します:

####################################################################
# 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することです私はループ内で項目をスキップするためにキーをチェックしたかったからです。

返信が遅れましたが、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
scroll top