配列の章で、配列について紹介しました。 このドキュメントでは、配列のさらに多くの使い方を紹介します。
前の配列の章では、@インデックスを付けた"${myarray[@]}"で、配列を個々の要素に展開する方法を見ました。
しかし、すべての要素を1つの文字列にまとめたいこともあります。
そのようなときは、*インデックスを使います。
echo "${myarray[*]}"
展開はダブルクォートで囲む必要があります。
Bashは、組み込み変数IFSの_最初の文字_を区切り文字として使います。
デフォルトでは、$IFSはスペース、タブ、改行で構成されています。
myarray=(one two three four)
mystring="${myarray[*]}"
declare -p mystring
# => declare -- mystring="one two three four"
IFS変数を操作して、別の区切り文字を使うこともできます。
myarray=(one two three four)
IFS=","
mystring="${myarray[*]}"
declare -p mystring
# => declare -- mystring="one,two,three,four"
join() {
local IFS=$1
shift
local elements=("$@")
echo "${elements[*]}"
}
join ":" "${myarray[@]}" # note, the "@" index
# => "one:two:three:four"
関数内でIFSをローカル化すれば、グローバルスコープで古い値を保存して元の値に戻す必要がありません。
さらに洗練させるなら、特殊パラメーター"$*"はダブルクォートで囲むと同じ働きをするので、関数の引数のコピーを保存する必要はありません。
join() {
local IFS=$1
shift
echo "$*"
}
(IFS=","; echo "${myarray[*]}")
括弧(())はサブシェル(現在のシェルのコピー)を作ります。括弧内のコマンドが完了するとサブシェルが終了し、変更されたIFS変数は消えます。
ただし、これはうまくいきません。IFS="," echo "${myarray[*]}"では、シェルが変更されたIFS変数をechoコマンドに適用する_前に_、パラメーター展開が実行されるからです。
変数の値の_部分文字列_に展開する"${variable:offset:length}"のパラメーター展開を見たことがあるかもしれません。
配列でも同じことができ、配列の一部を展開できます。
myarray=(one two three four)
subarray=("${myarray[@]:0:2}")
declare -p subarray
# => declare -a subarray=([0]="one" [1]="two")
subarray=("${myarray[@]:1:3}")
declare -p subarray
# => declare -a subarray=([0]="two" [1]="three" [2]="four")
長さの部分を省略すると、「オフセットから配列の末尾まで」という意味になります。
subarray=("${myarray[@]:2}")
declare -p subarray
# => declare -a subarray=([0]="three" [1]="four")
これは、知っているかもしれない他の言語ほど単純ではありません。 配列を関数に渡すには、主に2つの方法があります。
1つ目の方法では、配列のすべての値を渡し、関数内のローカル配列にまとめます。
myfunc() {
local array_copy=("$@")
# do stuff with array_copy
declare -p array_copy
}
array_original=(11 22 33 44)
myfunc "${array_original[@]}"
関数内の配列は値の_コピー_を保持します。 関数内で配列に加えた変更は、外側のスコープには反映されません。
この方法は、他の言語で知られている「参照渡し」に近いものです。
配列の_名前_を文字列として渡します。
関数は、nameref属性を持つローカル変数を作成します。
このローカル配列と、(名前を渡した)グローバル配列は_同じ配列_です。
myfunc() {
# note the `-n` option
local -n local_array=$1
# do stuff with local_array
for i in "${!local_array[@]}"; do
printf '%d => %s\n' "$i" "${local_array[i]}"
end
# we can mutate it
local_array+=(55 66 77)
}
array_original=(11 22 33 44)
myfunc "array_original"
# show the mutated array
declare -p array_original
# => declare -a array_original=([0]="11" [1]="22" [2]="33" [3]="44" [4]="55" [5]="66" [6]="77")
namerefは連想配列や、(文字列の値を保持する)「スカラー」変数でも機能します。
関数内でdeclare -p local_arrayを実行しても、あまり役には立ちません。
declare -n local_array="array_original"と出力されるだけです。
渡した配列の名前を調べれば、その配列の詳しい情報を取得できます。declare -p "$1"
ローカル配列には、渡した配列とは異なる名前を付けるよう注意してください。 コード自体は動作しますが、次のような「circular name reference」の警告が出力されます。
myfunc() {
local -n a=$1
local IFS=,
echo "${a[*]}"
}
# same name as the function's local variable
a=(one two three)
myfunc a
bash: local: warning: a: circular name reference
bash: warning: a: circular name reference
bash: warning: a: circular name reference
bash: warning: a: circular name reference
one,two,three
POSIX標準のみに準拠しようとするシェル(ashやdashなど)には、配列がありません。
もっとも近いのは、位置パラメーターを使うことです。
$1、$2などです。"$@"で個々の要素に展開されます。"$*"で1つの文字列に連結されます。$#です。値の代入にはsetコマンドを使います。
set -- one two three
set -- "$@" four
for item in "$@"; do
echo "do something with $item"
done
「移植性の高い」シェルスクリプトを書きたいなら、値の「リスト」を保存するために位置パラメーターを使うことになります。