回答:
Bash 4はこの機能をネイティブでサポートしています。ことを確認してくださいスクリプトのhashbangがある#!/usr/bin/env bash
か、#!/bin/bash
あなたが使用して終了しないようにsh
。あなたが直接あなたのスクリプトを実行していることを確認し、または実行するscript
とbash script
。(実際にはバッシュとのbashスクリプトを実行していないん起こる、とされ、本当に混乱!)
連想配列を宣言するには、次のようにします。
declare -A animals
通常の配列代入演算子を使用して、要素で埋めることができます。たとえば、次のマップが必要な場合animal[sound(key)] = animal(value)
:
animals=( ["moo"]="cow" ["woof"]="dog")
またはそれらをマージします:
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 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
関数に含めることはできません。declare
bash関数内で使用すると、ローカルで作成する変数がその関数のスコープに変わります。つまり、bash関数を使用してグローバル配列にアクセスしたり変更したりすることはできません。(bash 4ではdeclare -gを使用してグローバル変数を宣言できますが、bash 4では最初に連想配列を使用して、この回避策を回避できます。)
概要:
declare -A
、連想配列に使用します。declare
アップグレードできない場合は、このオプションを使用してください。awk
代わりに使用することを検討し、問題を完全に回避してください。4.x
しないはずy
です。
sudo port install bash
、(賢明なことに、IMHO)すべてのユーザーのPATH内のディレクトリを、明示的なプロセスごとの特権の昇格なしに書き込み可能にしたくない場合。
非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の方が優れていますが、ハックが必要な場合は、ハックだけで十分です。同様の手法で配列/ハッシュを検索できます。
VALUE=${animal#*:}
ケースを保護するために変更しますARRAY[$x]="caesar:come:see:conquer"
for animal in "${ARRAY[@]}"; do
これは私がここで探していたものです:
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行ずつ書き込み、次に '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`
ファイルシステムは、ハッシュマップとして使用できるツリー構造です。ハッシュテーブルは一時ディレクトリになり、キーはファイル名になり、値はファイルの内容になります。利点は、巨大なハッシュマップを処理でき、特定のシェルを必要としないことです。
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
もちろん、その遅いではなく、それは遅いです。私は自分のマシンでSSDとbtrfsを使用してテストしました。1秒あたり約3000エレメントの読み取り/書き込みを実行します。
mkdir -d
ますか?(Ubuntu 14では4.3ではありません。私はmkdir /run/shm/foo
、またはRAMがいっぱいになった場合に使用しますmkdir /tmp/foo
。)
mktemp -d
代わりに意味されたのでしょうか?
$value=$(< $hashtable/$key)
とはvalue=$(< $hashtable/$key)
どう違いますか?ありがとう!
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
${var#start}
は、変数varに格納されている値の先頭からテキストstartを削除します。
次のufwファイアウォールスクリプトからのコードスニペット内に示されているbash組み込み読み取りを使用するソリューションを検討してください。このアプローチには、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
IFS=$'|' read -r first rest <<< "$fields"
@lhunathやその他の人には、連想配列がBash 4を使用する方法であることに同意します。Bash3(OSX、更新できない古いディストリビューション)にこだわっている場合は、どこにでもあるはずのexprも使用できます。文字列と正規表現。辞書があまり大きくないときは特に好きです。
マップを文字列として記述します(区切り文字「、」も最初と最後にあることに注意してください)
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
私はAl Pの答えが本当に好きでしたが、独自性を安価に適用したいと思ったので、さらに一歩進んで-ディレクトリを使用しました。いくつかの明らかな制限(ディレクトリファイルの制限、無効なファイル名)がありますが、ほとんどの場合は機能します。
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()の追加
2つのことは、/ dev / shm(Redhat)を使用することにより、カーネル2.6で/ tmpの代わりにメモリを使用できることです。他のディストリビューションは異なる場合があります。また、hgetは次のようにreadを使用して再実装できます。
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エントリのハッシュでtime hgetを使用して次の結果が得られました。
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の内のハッシュテーブルを実装しました、そして、それは2010年3月に私のブログの記事からバージョン4に依存しないbashでハッシュテーブルを:
私が以前に使用されcksum
たハッシュではなくので、翻訳したJavaの文字列のハッシュコードをネイティブの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
や友人を除いて、ハッシュを必要とする可能性がある複雑さを伴うことはほとんどありません。
bash 4より前のバージョンでは、連想配列をbashで使用する良い方法はありません。あなたの最善の策は、awkのようなそのようなものを実際にサポートするインタープリター型言語を使用することです。一方、bash 4 はそれらをサポートしています。
以下のbash 3で良いの通り、ここかもしれないのヘルプより参照は次のとおりです。http://mywiki.wooledge.org/BashFAQ/006
Bash 3ソリューション:
いくつかの回答を読むにあたり、他の人を助けるために貢献したいと思います。
# Define a hash like this
MYHASH=("firstName:Milan"
"lastName:Adamovsky")
# Function to get value by key
getHashKey()
{
declare -a hash=("${!1}")
local key
local lookup=$2
for key in "${hash[@]}" ; do
KEY=${key%%:*}
VALUE=${key#*:}
if [[ $KEY == $lookup ]]
then
echo $VALUE
fi
done
}
# Function to get a list of all keys
getHashKeys()
{
declare -a hash=("${!1}")
local KEY
local VALUE
local key
local lookup=$2
for key in "${hash[@]}" ; do
KEY=${key%%:*}
VALUE=${key#*:}
keys+="${KEY} "
done
echo $keys
}
# Here we want to get the value of 'lastName'
echo $(getHashKey MYHASH[@] "lastName")
# Here we want to get all keys
echo $(getHashKeys MYHASH[@])
私はbash4の方法も使用しましたが、バグを見つけて迷惑です。
連想配列のコンテンツを動的に更新する必要があるため、次のように使用しました。
for instanceId in $instanceList
do
aws cloudwatch describe-alarms --output json --alarm-name-prefix $instanceId| jq '.["MetricAlarms"][].StateValue'| xargs | grep -E 'ALARM|INSUFFICIENT_DATA'
[ $? -eq 0 ] && statusCheck+=([$instanceId]="checkKO") || statusCheck+=([$instanceId]="allCheckOk"
done
bash 4.3.11では、dictの既存のキーに追加すると、すでに存在する場合は値が追加されることがわかりました。したがって、たとえば、いくつかの繰り返しの後、値の内容は「checkKOcheckKOallCheckOK」であり、これは良くありませんでした。
bash 4.3.39では問題はありません。既存のキーを追加することは、すでに存在する場合、実際の値を補うことを意味します。
cicleの前にstatusCheck連想配列をクリーニング/宣言するだけでこれを解決しました:
unset statusCheck; declare -A statusCheck
動的変数を使用してbash 3でHashMapを作成します。私はそれが私の答えでどのように機能するかを説明しました:シェルスクリプトの連想配列
また、bash 3で作成されたHashMap実装であるshell_mapを確認することもできます。