bash 配列入門 - 配列(array)と連想配列の使い方
この記事で解決できること
- bash の インデックス配列 と 連想配列 の違いと使い分けが分かる
- 宣言・参照・ループ・要素追加/削除の 基本操作 を一通り押さえられる
"${arr[@]}"のクォートや sparse 配列など 定番のハマり を回避できる
結論(先に要点)
- 順番付きの値の集まり → インデックス配列(
arr=(a b c)) - キーと値の対応表 → 連想配列(
declare -A mapが必須) - ループ・展開では 必ず
"${arr[@]}"とダブルクォートする
前提(対象環境)
- シェル: bash 4.0 以降(連想配列は 4.0+、負の添字は 4.3+)
sh/dashでは配列は使えない。#!/bin/bashを明示すること
配列とは何か?
結論: 配列は複数の値を 1 つの変数にまとめる仕組み。bash には順序を持つインデックス配列と、キーで引く連想配列の 2 種類がある。
通常のシェル変数は値を 1 つしか持てない。複数のファイル名やユーザー名をまとめて扱いたいとき、空白区切りの 1 文字列で済ませると空白を含む値で破綻する。配列なら各要素を独立した値として安全に保持できる。
bash の配列は 2 種類ある。
- インデックス配列(indexed array):
0から始まる整数の添字で要素を管理する - 連想配列(associative array): 任意の文字列をキーにして値を引く(ハッシュ/辞書に相当)
インデックス配列はどう使うのか?
結論:
arr=(a b c)で宣言し、${arr[0]}で個別要素、"${arr[@]}"で全要素を参照する。添字は省略すると先頭要素を指す。
宣言と代入
# まとめて宣言 fruits=(apple banana cherry) # 添字を指定して代入 fruits[3]=durian # 末尾に追加(+= 演算子) fruits+=(elderberry fig)
参照
echo "${fruits[0]}" # apple(最初の要素)
echo "${fruits[-1]}" # fig(末尾、bash 4.3+)
echo "${fruits[@]}" # 全要素をスペース区切りで
echo "${#fruits[@]}" # 要素数(5)
echo "${!fruits[@]}" # 全インデックス(0 1 2 3 4 5)$fruits のように添字なしで参照すると ${fruits[0]} と同じ(先頭要素のみ)になる。全要素を扱うつもりの取り違えに注意する。
スライス(部分取り出し)
echo "${fruits[@]:1:2}" # banana cherry(添字1から2個)連想配列はどう使うのか?
結論: 連想配列は使用前に
declare -Aで必ず宣言する。宣言を忘れるとインデックス配列扱いになり、キーが 0 に丸められて壊れる。
連想配列は任意の文字列をキーにできる。先に declare -A で宣言することが必須である点がインデックス配列との最大の違い。
declare -A capital # これを忘れると壊れる
capital[japan]=Tokyo
capital[france]=Paris
capital["united states"]=Washington
echo "${capital[japan]}" # Tokyo
echo "${!capital[@]}" # 全キー(順序は不定)
echo "${capital[@]}" # 全値(順序は不定)
echo "${#capital[@]}" # 要素数(3)declare -A を省略して capital[japan]=Tokyo とすると、bash は japan を 算術評価して 0 と解釈し、インデックス配列の添字 0 に代入してしまう。連想配列は宣言が前提。
連想配列は 順序を保持しない。挿入順やキー順での出力は保証されないため、整列したいなら sort に通す。
配列をループで回すには?
結論: 値を回すなら
for x in "${arr[@]}"、キー/添字を回すならfor k in "${!arr[@]}"。どちらもダブルクォートが必須。
# 値を順に処理する
for f in "${fruits[@]}"; do
echo "fruit: $f"
done
# キー(連想配列)を回して値を引く
for country in "${!capital[@]}"; do
echo "$country -> ${capital[$country]}"
doneクォートを外して for f in ${fruits[@]} と書くと、空白を含む要素が単語分割される。apple pie という 1 要素が apple と pie の 2 つに割れる。値の安全性のため常に "${arr[@]}" とする。
"${arr[@]}" と "${arr[*]}" の違いは?
結論:
[@]は各要素を別々の単語として展開し、[*]は IFS の先頭文字で連結した 1 つの文字列にする。ループには[@]を使う。
| 記法 | 展開結果 | 主な用途 |
|---|---|---|
"${arr[@]}" |
要素ごとに独立した単語 | ループ・引数渡し |
"${arr[*]}" |
IFS 先頭文字で連結した 1 文字列 | 表示・文字列化 |
arr=(one two three)
printf '[%s]\n' "${arr[@]}" # [one] [two] [three] が別行
printf '[%s]\n' "${arr[*]}" # [one two three] が1行
# 区切り文字を変えて連結する
IFS=,
echo "${arr[*]}" # one,two,three
unset IFS要素の追加・削除はどうするのか?
結論: 追加は
arr+=(...)、削除はunset 'arr[2]'。ただし unset したインデックス配列は添字が詰まらず sparse(穴あき)になる。
arr=(a b c d)
arr+=(e) # 末尾に追加 -> a b c d e
unset 'arr[1]' # 添字1(b)を削除
echo "${arr[@]}" # a c d e
echo "${!arr[@]}" # 0 2 3 4 ← 添字1が欠ける(詰まらない)unset 後の配列は 連番でなくなる(sparse 配列)。${#arr[@]} の要素数と最大添字が一致しなくなるため、添字を 0..N-1 で決め打ちするループは壊れる。詰め直すには再代入する。
arr=("${arr[@]}") # 添字を 0,1,2... に振り直すつまずきやすいポイントは?
結論: 配列は bash 専用、クォート必須、連想配列は declare -A 必須。この 3 点を外すと典型的な事故になる。
やってはいけない / 事故りやすい
#!/bin/shで配列を使う →dash等では構文エラー。#!/bin/bashを使うfor x in ${arr[@]}(クォートなし)→ 空白入り要素が分割されるdeclare -Aを忘れて連想配列を使う → キーが 0 に丸まり全要素が上書きされるarr[0]を$arrと書く → 全要素のつもりが先頭だけ
コマンド出力を配列に取り込むときは、行単位で安全に読む mapfile(readarray)が便利。
mapfile -t lines < <(ls -1) # 各行を1要素として lines に格納
echo "${#lines[@]}" # 行数-t は各行末尾の改行を取り除くオプション。これを付けないと要素に \n が残る。