배열 챕터에서 배열을 소개했어요. 이 문서에서는 배열을 사용하는 더 많은 방법을 살펴볼게요.
앞선 배열 챕터에서는 @ 인덱스와 함께 "${myarray[@]}"를 사용해서 배열을 각각의 원소로 확장하는 것을 봤어요.
그런데 때로는 모든 원소를 하나의 문자열로 합치고 싶을 때가 있어요.
이럴 때는 * 인덱스를 사용해요:
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")
이건 아는 다른 언어들처럼 간단하지 않아요. 함수에 배열을 전달하는 방법에는 크게 두 가지가 있어요.
첫 번째 방법은 배열의 모든 값을 전달하고, 함수 안에서 지역 배열로 모으는 거예요.
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"
지역 배열이 전달받은 배열과 다른 이름을 갖도록 주의해야 해요. 코드는 여전히 동작하지만, 다음과 같은 "순환 이름 참조" 경고가 나와요:
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 등."$@"를 사용하면 개별 원소로 확장돼요."$*"를 사용하면 하나의 문자열로 합쳐져요.$#예요.set 명령으로 값을 할당할 수 있어요:
set -- one two three
set -- "$@" four
for item in "$@"; do
echo "do something with $item"
done
"이식성 있는" 셸 스크립트를 작성하는 게 목표라면, 위치 매개변수를 사용해 값의 "목록"을 저장하게 돼요.