Pythonの辞書と同等のものは何ですか しかしBashでは(OS XとLinuxの間で動作するはずです).
Bash 4はこの機能をネイティブにサポートしています。スクリプトのハッシュバングが#!/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")
またはそれらをマージします。
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
がはるかに安全なオプションです。 eval
のようにbashコードのようにデータを評価するわけではないので、任意のコードを挿入することはそれほど簡単ではありません。
概念を紹介して答えを準備しましょう。
まず、間接化です。
$ 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
を使用すると、作成した変数 local がその関数の範囲になります。つまり、グローバル配列にアクセスしたり変更したりすることはできません。 (bash 4では、グローバル変数を宣言するためにdeclare -gを使用できますが、bash 4では、この回避策を回避して、最初から連想配列を使用できます。)
概要:
declare -A
を使用してください。declare
オプションを使用してください。awk
を使用することを検討し、問題を完全に回避してください。パラメータの置き換えもありますが、間接PCのように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」)。しかし、どちらにせよ、これはすべて非常にひどい、パフォーマンス的なものであることがわかります。
もしあなたが本当に速いハッシュルックアップを本当に望むなら、実際に本当にうまくいくひどい、ひどいハックがあります。それはこれです:あなたのkey/valuesを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`
ファイルシステムはハッシュマップとして使用できるツリー構造です。ハッシュテーブルは一時ディレクトリになり、キーはファイル名になり、値はファイルの内容になります。利点は巨大なハッシュマップを扱うことができ、特定のシェルを必要としないことです。
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
もちろん、遅いですがthat遅くはありません。私はSSDと btrfs を使って私のマシンでそれをテストしました、そしてそれは毎秒3000要素の読み書きを行います。
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
以下のufwファイアウォールスクリプトのコードスニペット内に示されているように、bash組み込み read を使用した解決策を検討してください。この方法には、必要なだけ(2つだけでなく)区切られたフィールドセットを使用するという利点があります。我々は を使用しましたポート範囲指定子にコロンが必要な場合があるため、 delimiter、つまり 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
連想配列がBash 4で動作する方法であることに@lhunathや他の人たちに同意します。あなたがBash 3(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()の追加
Bash 4より前のバージョンでは、bashで連想配列を使用する良い方法はありません。あなたの最善の策は、実際にawkのようなものをサポートしているインタプリタ言語を使うことです。一方、bash 4 は をサポートします。
Bash 3の less 良い方法に関しては、ここに参考になるかもしれません: 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[@])
同僚がこのスレッドに言及したばかりです。私は独自にbash内にハッシュテーブルを実装しましたが、バージョン4には依存していません。2010年3月の私のブログ投稿(ここでいくつかの回答の前に...) Hash tables in bash :
I 以前 ハッシュに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.6では/ tmpの代わりに/ dev/shm(Redhat)を使うことで/ tmpの代わりにメモリを使うことができます。以下のように、readを使ってhgetを再実装することもできます。
function hget {
while read key idx
do
if [ $key = $2 ]
then
echo $idx
return
fi
done < /dev/shm/hashmap.$1
}
さらに、すべてのキーが一意であると仮定することによって、returnは読み込みループを短絡し、すべてのエントリを読み飛ばす必要がなくなります。あなたの実装が重複したキーを持つことができるならば、単にリターンを省いてください。これはgrepとawkの両方を読んだりフォークしたりする費用を節約します。両方の実装で/ dev/shmを使用すると、最後のエントリを検索する3エントリのハッシュに対して以下のusing 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
を使用しているため、これはすべてfork over headに起因する可能性があります。
私はまた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で辞書の既存のキーに追加すると、すでに存在する場合は値を追加することになりました。したがって、たとえば、繰り返した後の値の内容は "checkKOcheckKOallCheckOK"であり、これは良くありませんでした。
Bash 4.3.39では、既存のキーを追加することでアクチュアリー値がすでに存在する場合はそれを代替することを意味しますが、問題ありません。
これを解決したのは、単にcicleの前でstatusCheck連想配列を消去/宣言するだけです。
unset statusCheck; declare -A statusCheck
動的変数を使用してbash 3でHashMapsを作成します。私はそれが私の答えの中でどのように働くかを説明しました: シェルスクリプトの中の連想配列
また、Shell_mapを見ることもできます。これは、bash 3で作成されたHashMap実装です。