701

Python辞書と同等ですが、Bashにあります(OS XとLinuxで機能するはずです)。

4

15 に答える 15

1190

Bash 4

Bash4はこの機能をネイティブにサポートしています。スクリプトのハッシュバンがである#!/usr/bin/env bashか、または#!/bin/bashを使用することにならないようにしてくださいsh。スクリプトを直接実行しているか、を使用して実行していることを確認してscriptくださいbash script。(BashでBashスクリプトを実際に実行しないことは実際に発生、非常に混乱します!)

次のようにして、連想配列を宣言します。

declare -A animals

通常の配列代入演算子を使用して、要素で埋めることができます。たとえば、次のマップが必要な場合animal[sound(key)] = animal(value)

animals=( ["moo"]="cow" ["woof"]="dog")

または、1行で宣言してインスタンス化します。

declare -A animals=( ["moo"]="cow" ["woof"]="dog")

次に、通常のアレイと同じように使用します。使用する

  • animals['key']='value'値を設定するには

  • "${animals[@]}"値を拡張するには

  • "${!animals[@]}"(に注意して!ください)キーを展開します

それらを引用することを忘れないでください:

echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done

Bash 3

bash 4より前は、連想配列はありません。 それらをエミュレートするために使用しないでくださいeval。シェルスクリプトの疫病であるevalため、疫病のように避けてください。最も重要な理由は、データを実行可能コードとして扱うことです(他にも多くの理由があります)。eval

何よりもまず、 bash 4へのアップグレードを検討してください。これにより、プロセス全体がはるかに簡単になります。

アップグレードできない理由がある場合declareは、はるかに安全なオプションです。データをbashコードのように評価evalしないため、任意のコードを簡単に挿入することはできません。

概念を紹介して答えを準備しましょう。

まず、間接参照。

$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow

第二に、declare

$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow

それらをまとめます:

# Set a value:
declare "array_$index=$value"

# Get a value:
arrayGet() { 
    local array=$1 index=$2
    local i="${array}_$index"
    printf '%s' "${!i}"
}

それを使用しましょう:

$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow

注:declare関数に入れることはできません。bash関数内で使用するとdeclare、作成された変数がその関数のスコープに対してローカルになります。つまり、bash関数を使用してグローバル配列にアクセスしたり変更したりすることはできません。(declare -gbash 4では、グローバル変数を宣言するために使用できますが、bash 4では、最初に連想配列を使用して、この回避策を回避できます。)

概要:

  • bash 4にアップグレードしてdeclare -A、連想配列に使用します。
  • declareアップグレードできない場合は、このオプションを使用してください。
  • 代わりに使用することを検討しawk、問題を完全に回避してください。
于 2010-08-12T13:09:35.597 に答える
139

パラメータの置換がありますが、PC 以外の場合もあります...間接的なように。

#!/bin/bash

# Array pretending to be a Pythonic dictionary
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

printf "%s is an extinct animal which likes to %s\n" "${ARRAY[1]%%:*}" "${ARRAY[1]##*:}"

もちろん、BASH 4 の方が優れていますが、ハックが必要な場合は...ハックだけで十分です。同様の手法で配列/ハッシュを検索できます。

于 2010-12-14T22:02:06.477 に答える
108

これは私がここで探していたものです:

declare -A hashmap
hashmap["key"]="value"
hashmap["key2"]="value2"
echo "${hashmap["key"]}"
for key in ${!hashmap[@]}; do echo $key; done
for value in ${hashmap[@]}; do echo $value; done
echo hashmap has ${#hashmap[@]} elements

これは、bash 4.1.5 ではうまくいきませんでした:

animals=( ["moo"]="cow" )
于 2011-05-23T00:30:08.823 に答える
29

hput()/hget() インターフェイスをさらに変更して、次のようにハッシュに名前を付けることができます。

hput() {
    eval "$1""$2"='$3'
}

hget() {
    eval echo '${'"$1$2"'#hash}'
}

その後

hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`

これにより、競合しない他のマップを定義できます (たとえば、首都で国を検索する「rcapitals」)。しかし、いずれにせよ、これはパフォーマンスの点で非常にひどいものであることがわかると思います。

本当に高速なハッシュ ルックアップが必要な場合は、実際には非常にうまく機能する恐ろしいハックがあります。これは次のとおりです。キー/値を一時ファイルに 1 行に 1 つずつ書き出してから、'grep "^$key"' を使用してそれらを取得し、cut、awk、sed などのパイプを使用して値を取得します。

私が言ったように、それはひどいように聞こえますし、遅くてあらゆる種類の不要な IO を実行する必要があるように聞こえますが、実際には非常に高速です (ディスクキャッシュは素晴らしいですね)。テーブル。キーの一意性を自分で強制する必要があります。数百のエントリしかない場合でも、出力ファイル/ grep コンボはかなり高速になります-私の経験では数倍高速です。また、メモリの消費も少なくなります。

これを行う1つの方法は次のとおりです。

hinit() {
    rm -f /tmp/hashmap.$1
}

hput() {
    echo "$2 $3" >> /tmp/hashmap.$1
}

hget() {
    grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };'
}

hinit capitals
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid

echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`
于 2010-02-08T23:38:29.257 に答える
17
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
于 2009-09-29T22:45:35.113 に答える
7

連想配列がBash 4で使用する方法であるという@lhunathなどに同意します。Bash 3(OSX、更新できない古いディストリビューション)に固執している場合は、exprも使用できます。これはどこにでもあるはずの文字列ですそして正規表現。辞書が大きすぎない場合は特に気に入っています。

  1. キーと値で使用しない 2 つの区切り文字を選択してください (例: ',' と ':' )
  2. マップを文字列として記述します (先頭と末尾にも区切り文字「,」があることに注意してください)。

    animals=",moo:cow,woof:dog,"
    
  3. 正規表現を使用して値を抽出する

    get_animal {
        echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
    }
    
  4. 文字列を分割してアイテムをリストします

    get_animal_items {
        arr=$(echo "${animals:1:${#animals}-2}" | tr "," "\n")
        for i in $arr
        do
            value="${i##*:}"
            key="${i%%:*}"
            echo "${value} likes to $key"
        done
    }
    

これで使用できます:

$ animal = get_animal "moo"
cow
$ get_animal_items
cow likes to moo
dog likes to woof
于 2014-04-17T23:05:58.300 に答える
5

私はAlPの答えが本当に好きでしたが、一意性を安価に適用したかったので、さらに一歩進んで、ディレクトリを使用しました。明らかな制限(ディレクトリファイルの制限、無効なファイル名)がいくつかありますが、ほとんどの場合は機能するはずです。

hinit() {
    rm -rf /tmp/hashmap.$1
    mkdir -p /tmp/hashmap.$1
}

hput() {
    printf "$3" > /tmp/hashmap.$1/$2
}

hget() {
    cat /tmp/hashmap.$1/$2
}

hkeys() {
    ls -1 /tmp/hashmap.$1
}

hdestroy() {
    rm -rf /tmp/hashmap.$1
}

hinit ids

for (( i = 0; i < 10000; i++ )); do
    hput ids "key$i" "value$i"
done

for (( i = 0; i < 10000; i++ )); do
    printf '%s\n' $(hget ids "key$i") > /dev/null
done

hdestroy ids

また、私のテストでは少しパフォーマンスが向上しています。

$ time bash hash.sh 
real    0m46.500s
user    0m16.767s
sys     0m51.473s

$ time bash dirhash.sh 
real    0m35.875s
user    0m8.002s
sys     0m24.666s

ピッチインしようと思っただけです。乾杯!

編集:hdestroy()の追加

于 2010-10-28T18:36:34.787 に答える
4

同僚がこのスレッドについて言及しました。私は独自に bash 内にハッシュ テーブルを実装しましたが、それはバージョン 4 に依存していません。2010 年 3 月の私のブログ投稿 (ここでの回答の前に...) からHash tables in bash :

以前cksumはハッシュを使用していましたが、 Java の文字列 hashCodeをネイティブの bash/zsh に変換しました。

# Here's the hashing function
ht() {
  local h=0 i
  for (( i=0; i < ${#1}; i++ )); do
    let "h=( (h<<5) - h ) + $(printf %d \'${1:$i:1})"
    let "h |= h"
  done
  printf "$h"
}

# Example:

myhash[`ht foo bar`]="a value"
myhash[`ht baz baf`]="b value"

echo ${myhash[`ht baz baf`]} # "b value"
echo ${myhash[@]} # "a value b value" though perhaps reversed
echo ${#myhash[@]} # "2" - there are two values (note, zsh doesn't count right)

双方向ではなく、組み込みの方法の方がはるかに優れていますが、実際にはどちらも使用しないでください。Bash は 1 回限りの迅速な処理のためのものであり、おそらくあなた~/.bashrcや友人を除いて、ハッシュを必要とするような複雑さが伴うことはめったにありません。

于 2012-10-18T00:39:57.350 に答える
3

2つ、/ dev / shm(Redhat)を使用することで、カーネル2.6で/tmpの代わりにメモリを使用できます。他のディストリビューションは異なる場合があります。また、hgetは、次のように読み取りを使用して再実装できます。

function hget {

  while read key idx
  do
    if [ $key = $2 ]
    then
      echo $idx
      return
    fi
  done < /dev/shm/hashmap.$1
}

さらに、すべてのキーが一意であると想定することにより、リターンは読み取りループを短絡し、すべてのエントリを読み取る必要がなくなります。実装に重複するキーが含まれる可能性がある場合は、リターンを省略してください。これにより、grepとawkの両方を読み取ってフォークする費用を節約できます。両方の実装に/dev/ shmを使用すると、最後のエントリを検索する3エントリのハッシュでtimehgetを使用して次のようになります。

Grep / Awk:

hget() {
    grep "^$2 " /dev/shm/hashmap.$1 | awk '{ print $2 };'
}

$ time echo $(hget FD oracle)
3

real    0m0.011s
user    0m0.002s
sys     0m0.013s

読み取り/エコー:

$ time echo $(hget FD oracle)
3

real    0m0.004s
user    0m0.000s
sys     0m0.004s

複数回の呼び出しで、50%未満の改善は見られませんでした。これはすべて、を使用しているため、頭上にフォークがあることに起因する可能性があります/dev/shm

于 2010-08-14T23:45:30.677 に答える
2

bash 4 より前では、bash で連想配列を使用する良い方法はありません。あなたの最善の策は、awkのように、実際にそのようなことをサポートしているインタープリター言語を使用することです. 一方、bash 4それらをサポートしています。

bash 3 であまり良くない方法については、参考になるかもしれません: http://mywiki.wooledge.org/BashFAQ/006

于 2010-08-12T12:53:37.040 に答える
-2

動的変数を使用して、bash 3 で HashMap を作成します。シェルスクリプトの連想配列への回答で、それがどのように機能するかを説明しました

また、bash 3 で作成された HashMap 実装であるshell_mapを調べることもできます。

于 2016-06-03T16:34:44.420 に答える