Python辞書と同等ですが、Bashにあります(OS XとLinuxで機能するはずです)。
15 に答える
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 -g
bash 4では、グローバル変数を宣言するために使用できますが、bash 4では、最初に連想配列を使用して、この回避策を回避できます。)
概要:
- bash 4にアップグレードして
declare -A
、連想配列に使用します。 declare
アップグレードできない場合は、このオプションを使用してください。- 代わりに使用することを検討し
awk
、問題を完全に回避してください。
パラメータの置換がありますが、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 の方が優れていますが、ハックが必要な場合は...ハックだけで十分です。同様の手法で配列/ハッシュを検索できます。
これは私がここで探していたものです:
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" )
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`
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 4で使用する方法であるという@lhunathなどに同意します。Bash 3(OSX、更新できない古いディストリビューション)に固執している場合は、exprも使用できます。これはどこにでもあるはずの文字列ですそして正規表現。辞書が大きすぎない場合は特に気に入っています。
- キーと値で使用しない 2 つの区切り文字を選択してください (例: ',' と ':' )
マップを文字列として記述します (先頭と末尾にも区切り文字「,」があることに注意してください)。
animals=",moo:cow,woof:dog,"
正規表現を使用して値を抽出する
get_animal { echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")" }
文字列を分割してアイテムをリストします
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
私は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()の追加
同僚がこのスレッドについて言及しました。私は独自に 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
や友人を除いて、ハッシュを必要とするような複雑さが伴うことはめったにありません。
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
。
bash 4 より前では、bash で連想配列を使用する良い方法はありません。あなたの最善の策は、awkのように、実際にそのようなことをサポートしているインタープリター言語を使用することです. 一方、bash 4はそれらをサポートしています。
bash 3 であまり良くない方法については、参考になるかもしれません: http://mywiki.wooledge.org/BashFAQ/006
動的変数を使用して、bash 3 で HashMap を作成します。シェルスクリプトの連想配列への回答で、それがどのように機能するかを説明しました
また、bash 3 で作成された HashMap 実装であるshell_mapを調べることもできます。