2013-12-05

連想配列 - Bash Advent Calendar - Day 5

今日は連想配列についてです。なんか予想以上に読者が少なくてモチベーションが上がりません。

初期化

基本的な書式は数値インデックスの配列(以下「普通の配列」)と変わりません。declare で指定する引数が大文字の A になるぐらい。
$ declare -A foo
$ declare -A foo=()
ただし、普通の配列の場合にはインデックスを指定しない場合には 0 から順に勝手にインデックス値を決めてくれましたが、(ある意味当然ですが)連想配列の場合は必ずインデックス文字列が必要です。
$ declare -A foo=("bar" "baz")
bash: foo: bar: 連想配列を設定するときには添字をつけなければいけません
bash: foo: baz: 連想配列を設定するときには添字をつけなければいけません
というわけで、
$ declare -A foo=(["bar"]="BAR" ["baz"]="BAZ")
$ declare -p foo
declare -A foo='([bar]="BAR" [baz]="BAZ" )'
でメデタシメデタシとなります。

一つ普通の配列と違うことは、declare 文(または typeset)が省略できないことです。
$ unset foo bar baz
$ foo=(["bar"]="BAR" ["baz"]="BAZ")
$ declare -p foo
declare -a foo='([0]="BAZ")'
一見すると意味不明かもしれませんが、これについては後述します。Bash の仕様としては、宣言されてない変数に対して配列を代入しようとした場合、それは常に普通の配列として初期化されるということになります。

値の代入、追加

細かいところはDay 1での説明を参照してください。常にインデックスが必要な以外は普通の配列と一緒です。+= の糞仕様も健在です。
$ declare -A foo=(["bar"]="BAR" ["baz"]="BAZ")
$ foo+=(["qux"]="QUX" ["bar"]="I'd like to overwrite this value.")
$ declare -p foo
declare -A foo='([bar]="BARI'\''d like to overwrite this value." [qux]="QUX" [baz]="BAZ" )'
というわけで、[bar]="I'd like to overwrite this value."ではなく、その前に BAR が残ったままです。

インデックスが存在するかのチェック

そういえば普通の配列のときにインデックスのチェック方法を説明してませんでしたね。今までに普通の配列でインデックスのチェックが必要になったことが一度もないので、完全に失念していました。

Bash では特別なインデックスチェックの文法はありません。ただ、変数展開では変数の存在チェックができるので、
$  declare -A foo=(["bar"]="BAR" ["baz"]="BAZ" ["qux"]="")
$  [[ "${foo[bar]+_}" == "_" ]]; echo $?
0
$  [[ "${foo[baz]+_}" == "_" ]]; echo $?
0
$  [[ "${foo[qux]+_}" == "_" ]]; echo $?
0
$  [[ "${foo[_]+_}" == "_" ]]; echo $?
1
といった感じで「変数が存在したら文字を置き換える」(${var[index]:+}ではなくコロンなしの${var[index]+}を使う)という変数展開を活用することでインデックスの存在チェックが可能です。

普通の配列との違い

今までちゃんと説明してませんでしたが、普通の配列と連想配列との大きな違いは、そのインデックスの型にあります。普通の配列のインデックスは数値型なので、Bash に算術評価式が使えます、もとい勝手に使われます。一方の連想配列では、インデックスは常に文字列です。

算術式評価だけで2・3日分はブログが書けるので細かいことはあえて説明しませんが、算術式評価では
  • 数値や演算子以外の文字列(で変数名として有効なもの)は変数名とみなされる
  • 未定義の変数の参照結果は 0
ということになります。なので
$ unset foo bar baz
$ foo=(["bar"]="BAR" ["baz"]="BAZ")
は、
  1. 初期化の項で述べた仕様により、右辺は普通の配列とみなす
  2. 普通の配列のインデックスは算術式評価の対象
  3. "bar" の算術式評価の結果は、bar という変数が存在しないので 0
  4. "baz" の算術式評価の結果は、bar という変数が存在しないので 0
となり、結果的に
$ unset foo bar baz
$ declare -a foo=([0]="BAR" [0]="BAZ")
と等価、インデックスの 0 が重複してるので最終的に
$ declare -p foo
declare -a foo='([0]="BAZ")'
という結果が得られるわけです。そんなこんなで、連想配列を使うときに declare -A を忘れたり、間違って declare -a とかすると、Bash はエラーを吐かない代わりに予測不能な挙動を示します。

では今日の結論。Bash スクリプトで連想配列がないと出来ないような処理をする必要があるのなら、他の LL を検討すべきですw Bash 遅いしね。




0 件のコメント:

prometheusのrate()関数の罠

 久しぶりのAdventカレンダー挑戦、うまくいく気がしません。 閑話休題。実のところ、rate()関数というよりは、サーバー側のmetric初期化問題です。 さて、何らかのサーバーAがあったとして、それが更に他のサーバーBにRPCを送っているとします。サーバーBの方でホワイトボ...