はじめに こんにちは、TIGの市川です。シェルスクリプト連載 の4日目です。
過去Shell Scriptでゴリゴリスクリプトを書いたりしていましたが(環境が許せば)Pythonで書くことが個人的に増えてきました。いざShell Scriptに戻ってきたときに配列と連想配列について調べ直すコトがままあったので、ここに記したいと思います。
declareとは 昨今Declarative programmingやらDeclarative APIやら、IT界隈でも形容詞で登場する単語ですが、Linuxのコマンドでdeclareというと変数を宣言する為のコマンドになります。
Shell Scriptではご存じの通り特に変数を宣言せずとも利用可能ですが、このコマンドとともに宣言すると色々な恩恵を受けることができます。
A="variable" declare B="variable" echo non_declarative: ${A} echo declarative: ${B} non_declarative: variable declarative: variable
declareのオプション
語り出してしまうとボリュームがそこそこになってしまうので、オプションの中の配列と連想配列について以降触れていきたいと思います。
Shell Scriptで配列 「declareで」といいながら実は配列はdeclareで宣言せずとも使えたりします。 ですが、せっかくなので宣言して使います。
data="a b c 1 2 3" declare -a list_declarative=(${data// / } )echo "================== Output ==================" echo "# すべての値の出力" echo ${list_declarative[@]} echo ${list_declarative[*]} echo "# 要素指定して出力" echo ${list_declarative[0]} echo "# 要素を削除" unset list_declarative[0]echo ${list_declarative[@]} echo "# 要素を追加(prepend, append)" list_declarative=("a" "${list_declarative[@]} " ) list_declarative=("${list_declarative[@]} " "4" ) echo ${list_declarative[@]} echo "# 要素を上書き" list_declarative[2]="hoge" echo ${list_declarative[@]} echo "# 要素数を確認" echo ${#list_declarative[@]} echo "# 値が存在するINDEXの一覧を確認" echo ${!list_declarative[@]} echo "# 添字の@と*の違い(ダブルクオートで囲ったときの動作が異なる)" echo "# 添字が@の場合は各要素が個別に出力される" for v in "${list_declarative[@]} " do echo "$v " done echo "添字が*の場合は各要素がIFSの最初の1文字で結合されて出力される" IFS=, for v in "${list_declarative[*]} " do echo "$v " done
================== Output ================== # すべての値の出力 a b c 1 2 3 a b c 1 2 3 # 要素指定して出力 a # 要素を削除 b c 1 2 3 # 要素を追加(prepend, append) a b c 1 2 3 4 # 要素を上書き a b hoge 1 2 3 4 # 要素数を確認 7 # 値が存在するINDEXの一覧を確認 0 1 2 3 4 5 6 # 添字の@と*の違い(ダブルクオートで囲ったときの動作が異なる) # 添字が@の場合は各要素が個別に出力される a b hoge 1 2 3 4 添字が*の場合は各要素がIFSの最初の1文字で結合されて出力される a,b,hoge,1,2,3,4
Shell Scriptで連想配列 連想配列を使う場合はdeclareが必須となっています。また、Bashであればversion4以降で利用可能です。
Pythonでいう辞書などと同様に、格納した順番は担保されません。
declare -A dict_declarative=([item_name]="Orange" [price]=100)echo "================== Output ==================" echo "# すべての値の出力" echo "${dict_declarative[@]} " echo "${dict_declarative[*]} " echo "# 要素指定して出力" echo "key = ${dict_declarative[item_name]} " echo "val = ${dict_declarative[price]} " echo "# 要素を削除" unset dict_declarative[item_name]echo ${dict_declarative[@]} echo "# 要素を追加(新規)" dict_declarative[item_name]=Apple echo ${dict_declarative[@]} echo "# 要素を追加(上書き)" dict_declarative[price]=120 echo ${dict_declarative[@]} echo "# 要素数を確認" echo ${#dict_declarative[@]} echo "# キーの一覧を確認" echo ${!dict_declarative[@]}
================== Output ================== # すべての値の出力 Orange 100 Orange,100 # 要素指定して出力 key = Orange val = 100 # 要素を削除 100 # 要素を追加 Apple 100 # 要素を上書き Apple 120 # 要素数を確認 2 # キーの一覧を確認 item_name price
bashの辞書で多次元配列 さて、ここで少々(個人的に)致命的な問題があるのですが、bashの配列・連想配列は1次元しか扱えないという点です。 他言語だとしれっと使えてしまうので、「えっ、なんで!?」となりがちですが、多次元配列「的な」ものを作ることでそれっぽい処理は可能です。
例えばPythonだとさらっと配列に辞書を含めるとかやりがち
from pprint import pprintnames = ['Alice' , 'Bob' , 'Mike' ] positions = ["Engineer" , "Manager" , "Developper" ] people = [ {"name" : name, "position" : position} for name, position in zip (names, positions) ] pprint(people)
[ {'name' : 'Alice' , 'position' : 'Engineer' }, {'name' : 'Bob' , 'position' : 'Manager' }, {'name' : 'Mike' , 'position' : 'Developper' } ]
bashの場合、多次元配列や配列内に連想配列を含められないため、KeyにIndexを持たせてしまうというハックがあります。
試しにCSVを読み込んで連想配列としつつ、一部ロジックを組み込んだ例を挙げます。従業員の今年度評定を基に翌年度の新給与を決めるロジック(的な)ものを処理します。
テスト用CSV(氏名, 年齢, 役職, 今年度評定)
Angela,29,Engineer,1800,A Ivory,35,Manager,3000,A Raeann,31,SeniorEngineer,2000,AA Violante,21,Assistant,1600,AAA
FILEPATH="test.csv" declare -A USER_DICTi=0 while IFS=, read name age position salary evaluation; do USER_DICT[${i} ,name]=${name} USER_DICT[${i} ,age]=${age} USER_DICT[${i} ,position]=${position} case "${evaluation} " in "A" ) USER_DICT[${i} ,salary]=$(echo ${salary} | awk '{printf "%4.0f", $1*1.0}' );; "AA" ) USER_DICT[${i} ,salary]=$(echo ${salary} | awk '{printf "%4.0f", $1*1.1}' );; "AAA" ) USER_DICT[${i} ,salary]=$(echo ${salary} | awk '{printf "%4.0f", $1*1.3}' );; esac let i++ done < ${FILEPATH} idx=$((${i} -1 )) for i in $(seq 0 ${idx} ); do echo ${USER_DICT[${i} ,name]} ${USER_DICT[${i} ,salary]} done
Angela 1800 Ivory 3000 Raeann 2200 Violante 2080
連想配列使うまでもないのですが、読み込んだ値からその後様々な処理をすることを考えると、連想配列は非常に扱いやすいため、覚えておいて損はないと思っています。 もし、Key, Value形式で値を持ちたい、使い回したいのであればオススメの実装です。
まとめ ということで今更ながらではありますが、bashの配列と連想配列についておさらいをしました。
個人的には文字列操作でなんとかなる領域を超えたら可読性や拡張性からPythonやGoなどを使うべし、と考えています。 ただし、現状もPython2.x系がデフォルトのOSがあったり、Goとか新規言語をインストールできない! などの制約があったり、bashでどうしても書かねばならないときにそっと頭の片隅においていただけると幸いです。
シェルスクリプト連載 の4日目でした。明日は澁川さんのシェルスクリプトでもGUI です!