シェルスクリプトの連想配列またはデータ構造のようなマップをシミュレートするスクリプトが必要でしたか?
Irfan's answer に追加するために、マップのコンテンツを反復する必要がないため、get()
の短縮版と高速版があります。
get() {
mapName=$1; key=$2
map=${!mapName}
value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
移植性が主な関心事でない場合の別のオプションは、シェルに組み込まれている連想配列を使用することです。これは、bash 4.0(ほとんどの主要なディストリビューションで利用可能ですが、自分でインストールしない限りOS Xでは利用できません)、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 newmap
の代わりにdeclare -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ステートメントもスローできます。 if [[$ var =〜/ blah /]]。または何でも。
一歩下がって、マップ、つまり連想配列が実際に何であるかを考える必要があると思います。それは、特定のキーの値を保存し、その値を迅速かつ効率的に取り戻す方法です。また、キーを繰り返し処理してすべてのキーと値のペアを取得したり、キーとそれに関連付けられた値を削除したりすることもできます。
次に、シェルスクリプトで常に使用するデータ構造について考えてください。スクリプトを作成せずにシェルだけで使用する場合でも、これらのプロパティがあります。困った?それはファイルシステムです。
実際、Shellプログラミングで連想配列を作成するために必要なのは、一時ディレクトリだけです。 mktemp -d
は連想配列コンストラクターです。
prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)
echo
とcat
を使用したくない場合は、いつでも小さなラッパーを作成できます。これらはIrfanからモデル化されていますが、$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
を呼び出さない限り、ディスクへの書き込みが保証されることはありません。寿命が短いこのような一時ファイルの場合、それらの多くが決してディスクに書き込まれないことはありそうにありません。
次のドライバープログラムを使用して、Irfanのコード、JerryのIrfanのコードの修正、および私のコードのベンチマークをいくつか行いました。
#!/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 実1m24.414s ユーザー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 実5m29.136s ユーザー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
は使用しないでください。これらは最もuいハックです。
サンプルコードを含む詳細な詳細な回答については、 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
今、この質問に答えています。
次のスクリプトは、シェルスクリプトの連想配列をシミュレートします。そのシンプルで非常に理解しやすい。
マップは、-name = Irfan --designation = SSE --company = My:SP:Own:SP:Companyとして保存されたkeyValuePairを持つ、終わりのないストリングです。
値の場合、スペースは「: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"
`
}
Bash 3には、ニースでシンプルな解決策がある特定のケースがあります。
多くの変数を処理したくない場合、またはキーが単に無効な変数識別子である場合、and配列には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
で一重引用符を使用することを忘れないでください。最初から静的/凍結されたハッシュには本当に便利ですが、hash_keys=()
配列からインデックスジェネレータを書くことができます。
気をつけてください、それは最初のものにデフォルトであるので、あなたは0番目の要素を取っておきたいかもしれません:
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[$?]}
または、長さを正しく保つために、インデックスを1つオフセットします。
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
以下のコマンドは、ダイナミック変数をキーとして、map _ $ {person}の形式ですべてを合計します。
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 Object、 Shell_map のように機能する関数を開発しています。
「HashMapインスタンス」を作成するために、Shell_map関数が作成できます異なる名前での自身のコピー。新しい関数のコピーごとに、異なる$ 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!"
私は以前に質問を見なかったなんて残念-私はライブラリ Shell-framework を書きました。最後のバージョンは here にあります。
例:
#!/bin/bash
#include map library
shF_PATH_TO_LIB="/usr/lib/Shell-framework"
source "${shF_PATH_TO_LIB}/map"
#simple example get/put
putMapValue "mapName" "mapKey1" "map Value 2"
echo "mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"
#redefine old value to new
putMapValue "mapName" "mapKey1" "map Value 1"
echo "after change mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"
#add two new pairs key/values and print all keys
putMapValue "mapName" "mapKey2" "map Value 2"
putMapValue "mapName" "mapKey3" "map Value 3"
echo -e "mapName keys are \n$(getMapKeys "mapName")"
#create new map
putMapValue "subMapName" "subMapKey1" "sub map Value 1"
putMapValue "subMapName" "subMapKey2" "sub map Value 2"
#and put it in mapName under key "mapKey4"
putMapValue "mapName" "mapKey4" "subMapName"
#check if under two key were placed maps
echo "is map mapName[mapKey3]? - $(if isMap "$(getMapValue "mapName" "mapKey3")" ; then echo Yes; else echo No; fi)"
echo "is map mapName[mapKey4]? - $(if isMap "$(getMapValue "mapName" "mapKey4")" ; then echo Yes; else echo No; fi)"
#print map with sub maps
printf "%s\n" "$(mapToString "mapName")"
数年前、他の機能(ロギング、構成ファイル、コマンドライン引数の拡張サポート、ヘルプの生成、単体テストなど)の中で連想配列をサポートするbashのスクリプトライブラリを作成しました。ライブラリには、連想配列のラッパーが含まれており、適切なモデルに自動的に切り替わります(bash4の内部および以前のバージョンのエミュレート)。これはシェルフレームワークと呼ばれ、origo.ethz.chでホストされていましたが、今日はリソースが閉じられています。まだ誰かがそれを必要とするならば、私はあなたとそれを共有することができます。
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 '"'
すでに述べたように、最高のパフォーマンスを実現する方法は、キー/値をファイルに書き出し、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アクションを実行することもできます。
シェルにはデータ構造のような組み込みのマップがありません。そのようなアイテムを記述するために生の文字列を使用します。
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
これは、他の人の答えよりも賢くはないように思えますが、新しい人にとってはシェルにとって理解しやすいようです。