配列を使用する

配列に値を設定する

配列名=(値1 値2 値3)

→ 値のリストで変数を初期化する。

括弧内に値のリストを指定したものを変数に設定することで、その変数を配列として使用することができる。

※ ksh の場合は set コマンドを使用して配列の設定を行う。括弧を使用した設定方法は使用できないこともあるので、ksh で配列を使用する場合は、set コマンドを使用する。

$ array=(111 "foo" 222 "bar" 333 "foobar")
#↑配列に値を設定する。

$ echo $array
111
#↑インデックスを指定しない場合は、先頭の値のみが出力される。

$ echo ${array[@]}
111 foo 222 bar 333 foobar
#↑全ての値を出力する場合は、インデックスに「@」を指定する。

ksh の場合は以下のように set コマンドを使用する。

$ set -A array 111 "foo" 222 "bar" 333 "foobar"
#↑ksh の場合は set コマンドに -A オプションを指定した上で、パラメータに値リストを指定する。

$ echo $array
111
$ echo ${array[@]}
111 foo 222 bar 333 foobar

括弧内には値の直接指定以外にも、変数の値やバッククオートを使用してコマンドの実行結果を指定することも可能だ。

$ date
2007年  5月 26日 土曜日 14:05:12 JST
$ array=(`date`)
$ echo ${array[3]}
土曜日
#↑date コマンドの実行結果が配列として変数 array に設定されている。
$ VAR="hoge fuga foo bar"
$ array=($VAR)
$ echo ${array[3]}
bar
#↑変数 VAR に設定されていた値が配列として変数 array に設定されている。

配列の各要素に値を設定する

配列名[インデックス]=

→ 配列の各値を個別に設定する場合はインデックスを指定する必要がある。

配列にインデックスを指定することで、配列の各要素に個別で値を設定することができる。また、括弧を使用して配列に設定した各値は、宣言した順に配列のインデックス 0、1、2、…、n に格納される。

つまり、array=("foo" "bar") と宣言した場合は、次のよう設定した場合と同一の結果になる。

  • array[0]="foo"
  • array[1]="bar"

インデックスに指定可能な値は数値のみで、任意の文字列を指定することはできない。連想配列を使用することはできない。使用してもエラーにはならないが、意図した通りの動きにはならない。

連想配列を作成したい場合は declare -A name で作成可能である。

配列に要素を追加する

単一要素を追加する

配列名+=()

→ 追加したい値を () で囲んで配列化しつつ += で配列に追加する。

追加したい要素を ()で配列化しつつ、追加先の配列に加算することで、要素を追加することができる。

$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}"
hoge, fuga, foo, bar,
#↑追加先の配列 (5つ目の要素はない)。

$ array+=("end")
#↑5つ目の要素を追加する。

$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}"
hoge, fuga, foo, bar, end
#↑5つ目の要素が追加されている。

複数要素を追加する

配列名+=(値1 値2 値3)

→ 複数の値を一度に追加する場合も、単一の場合と同様に () で配列化しつつ追加したい要素をスペース区切りで並べて加算する。

$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}"
hoge, fuga, foo, bar, ,
#↑追加先の配列 (5つ目、6つ目の要素はない)。

$ array+=("123" "456")
#↑2つの要素を一度に追加する。

$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}"
hoge, fuga, foo, bar, 123, 456
#↑5つ目、6つ目の要素が追加されている。

2つ以上の要素を一度に追加したい場合も、同様に () で配列化しつつ加算する。

$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}, ${array[6]}"
hoge, fuga, foo, bar, , ,
#↑追加先の配列 (5つ目、6つ目、7つ目の要素はない)。

$ array+=("123" "456" "789")
#↑3つの要素を一度に追加する。

$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}, ${array[6]}"
hoge, fuga, foo, bar, 123, 456, 789
#↑5つ目、6つ目、7つ目の要素が追加されている。

変数の値を要素として追加する

配列名+=($変数名)

→ 変数の値を配列の要素として追加する。

文字列や数値を指定するのと同様に、変数の値も追加可能だ。変数の値がスペース区切りの文字列である場合は複数の要素として設定されるので注意が必要だ。一つの要素として配列に追加したい場合は、変数を "" で囲むこと。

$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}"
hoge, fuga, foo, bar, ,
$ element="123 456"
#↑要素として追加する変数を作成。

$ array+=("$element")
#↑「""」で囲んで変数の値を 1 つの要素として追加する。

$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}"
hoge, fuga, foo, bar, 123 456,
#↑変数の値が 5 番目の要素として追加さている。

次は "" で囲まなかった場合の結果を見てみる。

$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}"
hoge, fuga, foo, bar, ,
$ element="123 456"
$ array+=($element)
#↑「""」で囲まずに変数の値を追加する。
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}"
hoge, fuga, foo, bar, 123, 456
#↑変数の値が 5 番目および 6 番目の要素として追加さている。

変数が展開された状態が、"" で囲んだ場合は array+=("123 456") となり、"" で囲まなかった場合は array+=(123 456) となるので、当然の結果ではあるがシェルスクリプトの経験が浅い人は見落としがちなので注意すること。

逆にこのことを利用し、スペース区切りで設定された変数の値を、複数の要素として配列に追加することも可能である

細かい差異で結果が大きく異なるため分かりづらい人もいるだろうが、この配列の場合に限らず、シェルスクリプトは変数展開後に最終的にどのような形でコマンドが実行されるかをイメージして作成するのが上達への近道である。

今回のような "" の有無による動作の違いに関しても、変数展開後にどのようなコマンドになっているかをイメージすると、すぐに結果を想像できるはずだ。

配列に要素を追加する方法には、

  1. 追加先の配列の全要素と追加したい値を配列化し再設定する方法 (e.g. array=("${array[@]}" "hoge")) や、
  2. 現在の要素数から配列末尾のインデックスを求め、そこに追加したい要素を設定する方法 (e.g. array[${#array[@]}]="hoge")

があるが、これらの方法だと追加先配列のインデックスが不連続だった場合に不具合が生じる

追加先の配列の全要素と追加したい値を配列化し、再設定する方法で要素を追加してみる。

$ array[0]="000"
$ array[2]="222"
$ array[5]="555"
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}, ${array[6]}"
000, , 222, , , 555,
#↑インデックスが不連続な配列を作成する。

$ array=("${array[@]}" "end")
#↑全要素と追加したい値を配列化して設定する。

$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}, ${array[6]}"
000, 222, 555, end, , ,
#↑不連続だったインデックスが連続したものに変化している。

この方法は配列への要素の追加ではなく、厳密には配列の再作成になるため、当然、不連続だったインデックスも再作成され、通常どおり 0 からの連番となる。

次に現在の要素数から配列末尾のインデックスを求め、そこに追加したい要素を設定する方法で要素を追加してみる。

$ array[0]="000"
$ array[2]="222"
$ array[5]="555"
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}, ${array[6]}"
000, , 222, , , 555,
#↑インデックスが不連続な配列を作成する。

$ array[${#array[@]}]="end"
#↑要素数をインデックスとして指定し、追加したい要素を設定する。

$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}, ${array[6]}"
000, , 222, end, , 555,
#↑配列の末尾ではなく、3つ目の要素として設定されている。

配列のインデックスが連続している場合は、配列の「要素数=末尾(最後の要素の次の)のインデックス」となるが、インデックスが不連続であれば当然これは成り立たない。

上記の例でも要素数が 3 つであるために array[3]="end" が実行され、インデックスが 3、つまり 4 つ目の要素として設定されている。

+= を使用して要素を追加することで、この問題は解決できる。

$ array[0]="000"; array[2]="222"; array[5]="555"
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}, ${array[6]}"
000, , 222, , , 555,
#↑インデックスが不連続な配列を作成する。

$ array+=("end")
#↑「+=」で配列に要素を追加する。

$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}, ${array[5]}, ${array[6]}"
000, , 222, , , 555, end
#↑インデックスが不連続でも配列末尾に追加されている。

配列に要素を追加する場合は「+=」を使用するのが無難だ

また、+= を使用する場合は、追加したい値を () で配列化するのを忘れないこと。次のように**()で配列化せずに追加すると、先頭の要素に文字列として追加されるだけになる**ので注意が必要だ。

$ array=("hoge" "fuga" "foo" "bar")
$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}"
hoge, fuga, foo, bar,
$ array+="end"
#↑配列化せずに追加する。

$ echo "${array[0]}, ${array[1]}, ${array[2]}, ${array[3]}, ${array[4]}"
hogeend, fuga, foo, bar,
#↑先頭の要素の値の末尾に追加されている。

配列の要素を参照する

各要素を参照する

${配列名[インデックス]}

→ 配列にインデックスを指定すると各要素の値のみを参照できる。

配列の参照方法は変数と同じように $ を使用するが、必ずインデックス部分を含めた変数名全体を {} で囲む必要がある

$ date
2007年  5月 26日 土曜日 14:25:04 JST
$ array=(`date`)
$ echo ${array[0]}
2007年
#↑インデックス部分も含めて{}で囲む。

$ index=3
$ echo ${array[$index]}
土曜日
#↑インデックスには変数を指定することも可能。

$ echo $array
2007年
#↑インデックスを指定しないと先頭の値が参照される。

$ echo $array[3]
2007年[3]
#↑インデックスを指定する場合は{}を省略すると正しく参照することができない。

全要素を参照する

配列に設定されている全ての値を一度に参照することも可能。

${配列名[@]}

→ インデックスに @ を指定することで配列内の全ての値がスペース区切りで出力される。

ちなみに @ 以外にも * が使用可能。この二つの違いは、前述してある $@$* の違いと同じ。

$ array=("foo" "bar" "hoge" "fuga" "HOGE HOGE" "FUGA FUGA")
$ echo ${array[@]}
foo bar hoge fuga HOGE HOGE FUGA FUGA
#↑インデックスに「@」を指定したので配列の全要素値がスペース区切りで出力されている。

$ for i in "${array[@]}"
> do
>   echo $i
> done
foo
bar
hoge
fuga
HOGE HOGE
FUGA FUGA

$ for i in "${array[*]}"
> do
>   echo $i
> done
foo bar hoge fuga HOGE HOGE FUGA FUGA

インデックスに @ を使用した配列の参照は、for 文を使用する場合に非常に便利である。以下はその例 (atmark.sh)。

#!/bin/bash

# カレントディレクトリのファイル名リストを配列に格納
files=(`ls -1`)

# インデックスに @ を指定して、全ての要素を for 文の値リストに指定
for file in ${files[@]}
do
  # 各ファイルのファイルサイズを取得
  size=`ls -l $file | awk '{print $5}'`

  echo "FILE: $file - $size byte"
done

exit 0

このシェルスクリプト atmark.sh の実行結果は、以下のとおりとなる。

$ 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 文のような処理を簡単に記述することができる。

配列の要素数を参照する

${#配列名[*]}

→ 配列名の先頭に # を付けて配列全体 (インデックスに *を指定) を参照する。

変数名に # を付加した参照では変数の設定値の文字数に展開されるが、配列名 (正確には配列名と [*] ) に # を付加して参照すると、配列の全要素数に展開される。

$ array=("foo" "bar" "hoge" "fuga" "HOGE HOGE" "FUGA FUGA")
$ echo "${#array[*]}"
6
$ echo "${#array[@]}"
6
#↑インデックスに "@" を指定しても結果は同じ。

配列の要素でループする

for i in "${array[@]}"
do
  ...
done

→for 文にダブルクォートで囲みインデックスに @ を指定した配列を渡す。

配列の全要素に対して処理を行いたい場合などで、配列の要素でループ処理を行いたい場合は、for 文のループ対象としてダブルクォートで囲みインデックスに @ を指定した配列を渡す。

注意点は以下の 2つ。

  1. 要素にスペースが含まれることを考慮してダブルクォートで囲むこと
  2. * ではなく @ を使用すること

要素にスペースが入っていないことを保証できるのであれば、ダブルクォートを使用する必要もなく *@ の使い分けも不要であるが、ベストプラクティスとして配列でループ処理を行う場合はダブルクォートと @ を使用すると覚えてほしい

ちなみに、ダブルクォートと * を使用するとシェルが配列全体を一つの値として展開するので、要素が何個あろうともループが一回で終了してしまう。

$ # 「*」を使用してしまったダメな例
$ array=(1 2 3 4 5)
$ for i in "${array[*]}"
> do
>  echo "[${i}]"
> done
[1 2 3 4 5]
#↑要素が5個あるのに、ループが1回で終了している。

インデックスに * を指定した配列を for 文に渡すと、シェルは for i in "1 2 3 4 5" と解釈するため、意図したとおりのループにはならない。

$ # 「@」を使用した正しい例
$ array=(1 2 3 4 5)
$ for i in "${array[@]}"
> do
>  echo "[${i}]"
> done
[1]
[2]
[3]
[4]
[5]
#↑要素数分のループ処理が実行される。

インデックスに @ を指定することで、シェルは for i in "1" "2" "3" "4" "5" と解釈し、要素数分のループ処理を行うことができる。

また、ダブルクォートを使用しなければ、*@ のどちらを使用しても結果は同じになるが、以下のように要素にスペースが含まれる場合に問題が発生する。

# 配列の要素がスペースを含んでいる場合 (ダブルクォート不使用)
$ array=(1 2 3 4 5 "6 6")
$ for i in ${array[*]}
> do
>   echo "[${i}]"
> done
[1]
[2]
[3]
[4]
[5]
[6]
[6]
#↑"6 6" がダブルクォートがないために 2つの要素と解釈されるためループ回数が 1回多くなっている。

$ for i in ${array[@]}
> do
>   echo "[${i}]"
> done
[1]
[2]
[3]
[4]
[5]
[6]
[6]
#↑"6 6" がダブルクォートがないために 2つの要素と解釈されるためループ回数が 1回多くなっている。

ダブルクォートを使用しなければ、シェルは for i in 1 2 3 4 5 6 6 と解釈する。そのため要素「6 6」を 2つの要素とみなされてしまう。

このような問題を避けるため、繰り返しになるが、ベストプラクティスとして配列でループ処理を行う場合はダブルクォートと @ を使用すること。

$ array=(1 2 3 4 5 "6 6")
$ for i in "${array[@]}"
> do
>   echo "[${i}]"
> done
[1]
[2]
[3]
[4]
[5]
[6 6]
#↑ダブルクォートと「@」を使用することでスペースを含む要素にも対応可能。

配列の要素をソートする

文字列としてソートする

_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 を変更しているのはそのためである。

$ 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"
#↑事前にIFSを改行のみに変更し、ソート後の出力を配列に再設定する。

$ for item in "${array[@]}"; do echo "$item"; done
111 111
222 222
333 333
aaa aaa
bbb bbb
ccc ccc
#↑実行後、配列にはソート済みの値が設定されている。

数値としてソートする

要素を文字列としてではなく、数値としてソートするには、sort コマンドに -n オプションを指定する。

array=(`for item in "${array[@]}"; do echo "$item"; done | sort -n`)

-n オプションを指定し、数値としてソートした上で配列に格納する。

配列の要素が数値のみである場合は、空白を含むことを想定しないので、IFS を変更する必要はない。

$ 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
#↑数値としてソートしたものが配列に格納されている。