配列を使用する
配列に値を設定する
code:shell
配列名=(値1 値2 値3)
→ 値のリストで変数を初期化する。
括弧内に値のリストを指定したものを変数に設定することで、その変数を配列として使用することができる。 ※ ksh の場合は set コマンドを使用して配列の設定を行う。括弧を使用した設定方法は使用できないこともあるので、ksh で配列を使用する場合は、set コマンドを使用する。
code: shell
$ array=(111 "foo" 222 "bar" 333 "foobar")
$ echo $array
111
111 foo 222 bar 333 foobar
ksh の場合は以下のように set コマンドを使用する。
code: shell
$ set -A array 111 "foo" 222 "bar" 333 "foobar"
#↑ksh の場合は set コマンドに -A オプションを指定した上で、パラメータに値リストを指定する。 $ echo $array
111
111 foo 222 bar 333 foobar
括弧内には値の直接指定以外にも、変数の値やバッククオートを使用してコマンドの実行結果を指定することも可能だ。
code: shell
$ date
2007年 5月 26日 土曜日 14:05:12 JST
$ array=(date)
土曜日
#↑date コマンドの実行結果が配列として変数 array に設定されている。 code: shell
$ VAR="hoge fuga foo bar"
$ array=($VAR)
bar
#↑変数 VAR に設定されていた値が配列として変数 array に設定されている。 配列の各要素に値を設定する
code: shell
→ 配列の各値を個別に設定する場合はインデックスを指定する必要がある。
配列にインデックスを指定することで、配列の各要素に個別で値を設定することができる。また、括弧を使用して配列に設定した各値は、宣言した順に配列のインデックス 0、1、2、...、n に格納される。
つまり、array=("foo" "bar") と宣言した場合は、次のよう設定した場合と同一の結果になる。
array[0]="foo"
array[1]="bar"
インデックスに指定可能な値は数値のみで、任意の文字列を指定することはできない。連想配列を使用することはできない。使用してもエラーにはならないが、意図した通りの動きにはならない。
連想配列を作成したい場合は declare -A name で作成可能である。
配列に要素を追加する
単一要素を追加する
code: shell
配列名+=(値)
→ 追加したい値を () で囲んで配列化しつつ += で配列に追加する。
追加したい要素を ()で配列化しつつ、追加先の配列に加算することで、要素を追加することができる。
code: shell
$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}" hoge, fuga, foo, bar,
$ array+=("end")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}" hoge, fuga, foo, bar, end
複数要素を追加する
code: shell
配列名+=(値1 値2 値3)
[* → 複数の値を一度に追加する場合も、単一の場合と同様に () で配列化しつつ追加したい要素をスペース区切りで並べて加算する。]
code: shell
$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}" hoge, fuga, foo, bar, ,
$ array+=("123" "456")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}" hoge, fuga, foo, bar, 123, 456
2つ以上の要素を一度に追加したい場合も、同様に () で配列化しつつ加算する。
code: shell
$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}, ${array6}" hoge, fuga, foo, bar, , ,
$ array+=("123" "456" "789")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}, ${array6}" hoge, fuga, foo, bar, 123, 456, 789
変数の値を要素として追加する
code: shell
配列名+=($変数名)
→ 変数の値を配列の要素として追加する。
文字列や数値を指定するのと同様に、変数の値も追加可能だ。変数の値がスペース区切りの文字列である場合は複数の要素として設定されるので注意が必要だ。一つの要素として配列に追加したい場合は、変数を "" で囲むこと。
code: shell
$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}" hoge, fuga, foo, bar, ,
$ element="123 456"
$ array+=("$element")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}" hoge, fuga, foo, bar, 123 456,
次は "" で囲まなかった場合の結果を見てみる。
code: shell
$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}" hoge, fuga, foo, bar, ,
$ element="123 456"
$ array+=($element)
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}" hoge, fuga, foo, bar, 123, 456
変数が展開された状態が、"" で囲んだ場合は array+=("123 456") となり、"" で囲まなかった場合は array+=(123 456) となるので、当然の結果ではあるがシェルスクリプトの経験が浅い人は見落としがちなので注意すること。
逆にこのことを利用し、スペース区切りで設定された変数の値を、複数の要素として配列に追加することも可能である。
細かい差異で結果が大きく異なるため分かりづらい人もいるだろうが、この配列の場合に限らず、シェルスクリプトは変数展開後に最終的にどのような形でコマンドが実行されるかをイメージして作成するのが上達への近道である。
今回のような "" の有無による動作の違いに関しても、変数展開後にどのようなコマンドになっているかをイメージすると、すぐに結果を想像できるはずだ。
配列に要素を追加する方法には、
1. 追加先の配列の全要素と追加したい値を配列化し再設定する方法 (e.g. array=("${array[@]}" "hoge")) や、
2. 現在の要素数から配列末尾のインデックスを求め、そこに追加したい要素を設定する方法 (e.g. array[${#array[@]}]="hoge")
があるが、これらの方法だと追加先配列のインデックスが不連続だった場合に不具合が生じる。
追加先の配列の全要素と追加したい値を配列化し、再設定する方法で要素を追加してみる。
code: shell
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}, ${array6}" 000, , 222, , , 555,
$ array=("${array@}" "end") $ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}, ${array6}" 000, 222, 555, end, , ,
この方法は配列への要素の追加ではなく、厳密には配列の再作成になるため、当然、不連続だったインデックスも再作成され、通常どおり 0 からの連番となる。
次に現在の要素数から配列末尾のインデックスを求め、そこに追加したい要素を設定する方法で要素を追加してみる。
code: shell
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}, ${array6}" 000, , 222, , , 555,
$ array[${#array@}]="end" $ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}, ${array6}" 000, , 222, end, , 555,
配列のインデックスが連続している場合は、配列の「要素数=末尾(最後の要素の次の)のインデックス」となるが、インデックスが不連続であれば当然これは成り立たない。
上記の例でも要素数が 3 つであるために array[3]="end" が実行され、インデックスが 3、つまり 4 つ目の要素として設定されている。
+= を使用して要素を追加することで、この問題は解決できる。
code: shell
$ array0="000"; array2="222"; array5="555" $ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}, ${array6}" 000, , 222, , , 555,
$ array+=("end")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}, ${array5}, ${array6}" 000, , 222, , , 555, end
配列に要素を追加する場合は「+=」を使用するのが無難だ。
また、+= を使用する場合は、追加したい値を () で配列化するのを忘れないこと。次のように**()で配列化せずに追加すると、先頭の要素に文字列として追加されるだけになる**ので注意が必要だ。
code: shell
$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}" hoge, fuga, foo, bar,
$ array+="end"
$ echo "${array0}, ${array1}, ${array2}, ${array3}, ${array4}" hogeend, fuga, foo, bar,
配列の要素を参照する
各要素を参照する
code: shell
→ 配列にインデックスを指定すると各要素の値のみを参照できる。
配列の参照方法は変数と同じように $ を使用するが、[* 必ずインデックス部分を含めた変数名全体を {} で囲む必要がある]。
code: shell
$ date
2007年 5月 26日 土曜日 14:25:04 JST
$ array=(date)
2007年
$ index=3
土曜日
$ echo $array
2007年
全要素を参照する
配列に設定されている全ての値を一度に参照することも可能。
code: shell
[* → インデックスに @ を指定することで配列内の全ての値がスペース区切りで出力される。]
ちなみに @ 以外にも * が使用可能。この二つの違いは、前述してある $@ と $* の違いと同じ。
code: shell
$ array=("foo" "bar" "hoge" "fuga" "HOGE HOGE" "FUGA FUGA")
foo bar hoge fuga HOGE HOGE FUGA FUGA
do
echo $i
done
foo
bar
hoge
fuga
HOGE HOGE
FUGA FUGA
do
echo $i
done
foo bar hoge fuga HOGE HOGE FUGA FUGA
インデックスに @ を使用した配列の参照は、for 文を使用する場合に非常に便利である。以下はその例 (atmark.sh)。
code: shell
# カレントディレクトリのファイル名リストを配列に格納
files=(ls -1)
# インデックスに @ を指定して、全ての要素を for 文の値リストに指定
do
# 各ファイルのファイルサイズを取得
size=ls -l $file | awk '{print $5}'
echo "FILE: $file - $size byte"
done
exit 0
このシェルスクリプト atmark.sh の実行結果は、以下のとおりとなる。
code: shell
$ ls -l
合計 24
-rwxr-xr-x 1 sunone sunone 372 5月 26 14:46 atmark.sh
-rw-rw-r-- 1 sunone sunone 165 5月 26 14:44 file1
-rw-rw-r-- 1 sunone sunone 531 5月 26 14:44 file2
-rw-rw-r-- 1 sunone sunone 655 5月 26 14:44 file3
-rw-rw-r-- 1 sunone sunone 1454 5月 26 14:45 file4
-rw-rw-r-- 1 sunone sunone 541 5月 26 14:45 file5
$ ./atmark.sh
FILE: atmark.sh - 372 byte
FILE: file1 - 165 byte
FILE: file2 - 531 byte
FILE: file3 - 655 byte
FILE: file4 - 1454 byte
FILE: file5 - 541 byte
各要素に対する処理を行う場合には、for 文のリストにインデックスに「@」を指定した配列を指定すると、foreach 文のような処理を簡単に記述することができる。 配列の要素数を参照する
code: shell
[* → 配列名の先頭に # を付けて配列全体 (インデックスに *を指定) を参照する。]
変数名に # を付加した参照では変数の設定値の文字数に展開されるが、配列名 (正確には配列名と [*] ) に # を付加して参照すると、配列の全要素数に展開される。
code: shell
$ array=("foo" "bar" "hoge" "fuga" "HOGE HOGE" "FUGA FUGA")
6
6
配列の要素でループする
code: shell
do
...
done
[* →for 文にダブルクォートで囲みインデックスに @ を指定した配列を渡す。]
配列の全要素に対して処理を行いたい場合などで、配列の要素でループ処理を行いたい場合は、for 文のループ対象としてダブルクォートで囲みインデックスに @ を指定した配列を渡す。
注意点は以下の 2つ。
1. 要素にスペースが含まれることを考慮してダブルクォートで囲むこと
2. * ではなく @ を使用すること
要素にスペースが入っていないことを保証できるのであれば、ダブルクォートを使用する必要もなく * と @ の使い分けも不要であるが、[* ベストプラクティスとして配列でループ処理を行う場合はダブルクォートと @ を使用すると覚えてほしい]。
ちなみに、ダブルクォートと * を使用するとシェルが配列全体を一つの値として展開するので、要素が何個あろうともループが一回で終了してしまう。
code: shell
$ # 「*」を使用してしまったダメな例
$ array=(1 2 3 4 5)
do
done
インデックスに * を指定した配列を for 文に渡すと、シェルは for i in "1 2 3 4 5" と解釈するため、意図したとおりのループにはならない。
code: shell
$ # 「@」を使用した正しい例
$ array=(1 2 3 4 5)
do
done
インデックスに @ を指定することで、シェルは for i in "1" "2" "3" "4" "5" と解釈し、要素数分のループ処理を行うことができる。
また、ダブルクォートを使用しなければ、* と @ のどちらを使用しても結果は同じになるが、以下のように要素にスペースが含まれる場合に問題が発生する。
code: shell
# 配列の要素がスペースを含んでいる場合 (ダブルクォート不使用)
$ array=(1 2 3 4 5 "6 6")
do
done
#↑"6 6" がダブルクォートがないために 2つの要素と解釈されるためループ回数が 1回多くなっている。 do
done
#↑"6 6" がダブルクォートがないために 2つの要素と解釈されるためループ回数が 1回多くなっている。 ダブルクォートを使用しなければ、シェルは for i in 1 2 3 4 5 6 6 と解釈する。そのため要素「6 6」を 2つの要素とみなされてしまう。
このような問題を避けるため、繰り返しになるが、[* ベストプラクティスとして配列でループ処理を行う場合はダブルクォートと @ を使用する]こと。
code: shell
$ array=(1 2 3 4 5 "6 6")
do
done
配列の要素をソートする
文字列としてソートする
code: shell
_IFS="$IFS"; IFS="\n"; array=(for item in "${array[@]}"; do echo "$item"; done | sort); IFS="$_IFS"
→配列の全要素を出力した上で sort コマンドでソートを実行し、実行結果を再度配列に格納する。
配列の要素に空白が含まれる場合を考慮して、事前に IFS を改行のみに変更しておく。こうしておくことで、ソート結果の配列への再設定時に空白区切りで要素が設定されるのを防止する。変更した IFS は、最後に元に戻す。
配列は for 文に "${array[@]}" の様に全要素を意味する @ 使用し、ダブルクォートで囲んだ上で指定する。この指定により、各要素がダブルクォートで囲まれた状態で配列が展開される。
つまり、for 文で使用されている変数 item に配列の (要素の空白区切り単位ではなく) 各要素単位で値が代入される。
さらに各要素単位で echo コマンドで出力することにより、全要素が改行区切りで出力されるため、sort コマンドによるソートが可能になる。
ソートされた全要素を改行区切りで再度配列に設定することで、配列の全要素をソートすることが可能になる。配列に再設定される時点で IFS が改行のみになっていない場合は、改行と空白区切りで配列に再設定されることになるため、事前に IFS を改行のみに変更しておく必要があるがある。最初に IFS を変更しているのはそのためである。
code: shell
$ array=("222 222" "ccc ccc" "aaa aaa" "111 111" "bbb bbb" "333 333")
$ for item in "${array@}" do
echo $item
done
222 222
ccc ccc
aaa aaa
111 111
bbb bbb
333 333
_IFS="$IFS";IFS="\n";array=(for item in "${array[@]}"; do echo "$item"; done | sort);IFS="$_IFS"
$ for item in "${array@}"; do echo "$item"; done 111 111
222 222
333 333
aaa aaa
bbb bbb
ccc ccc
数値としてソートする
要素を文字列としてではなく、数値としてソートするには、sort コマンドに -n オプションを指定する。
code: shell
array=(for item in "${array[@]}"; do echo "$item"; done | sort -n)
[* →-n オプションを指定し、数値としてソートした上で配列に格納する。]
配列の要素が数値のみである場合は、空白を含むことを想定しないので、IFS を変更する必要はない。
code: shell
$ array=(2 3 04 000 001)
$ for item in "${array@}"; do echo "$item"; done 2
3
04
000
001
$ for item in "${array@}"; do echo "$item"; done | sort 000
001
04
2
3
$ for item in "${array@}"; do echo "$item"; done | sort -n 000
001
2
3
04
$ array=(for item in "${array[@]}"; do echo "$item"; done | sort -n)
$ for item in "${array@}"; do echo "$item"; done 000
001
2
3
04