트랙
/
Bash
Bash
/
학습 과정
/
배열 더 알아보기
배열

배열 더 알아보기 에서 Bash

{other: "%{count}개의 연습 문제"}

배열 더 알아보기 소개

배열 챕터에서 배열을 소개했어요. 이 문서에서는 배열을 사용하는 더 많은 방법을 살펴볼게요.

배열의 원소를 하나의 문자열로 합치기

앞선 배열 챕터에서는 @ 인덱스와 함께 "${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"
Advanced
이 내용을 함수로 감쌀 수 있어요. 자세히 보려면 클릭하세요.
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를 수정하는 것이 현재 셸에서 수정하는 것을 피하는 좋은 방법이에요. 자세히 보려면 클릭하세요.
(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는 연관 배열과 (문자열 값을 담는) "스칼라" 변수에도 사용할 수 있어요.

Note

함수 안에서 declare -p local_array는 그다지 유용하지 않아요. 그냥 declare -n local_array="array_original"만 출력할 뿐이에요. 배열에 대한 자세한 정보가 필요하면 전달받은 배열 이름을 직접 살펴보면 돼요: declare -p "$1"

Caution

지역 배열이 전달받은 배열과 다른 이름을 갖도록 주의해야 해요. 코드는 여전히 동작하지만, 다음과 같은 "순환 이름 참조" 경고가 나와요:

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

"이식성 있는" 셸 스크립트를 작성하는 게 목표라면, 위치 매개변수를 사용해 값의 "목록"을 저장하게 돼요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요