0Pricing
Linux Command Line & Bash Scripting Mastery · 강의

함수 간 배열과 연관 맵 전달하기

nameref와 간접 확장을 사용하여 복잡한 데이터 구조를 함수 안팎으로 안전하게 전달하는 방법을 배웁니다.

함수 간 배열과 연관 맵 전달하기은(는) CoddyKit의 무료 Linux Command Line & Bash Scripting Mastery 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Linux Command Line & Bash Scripting Mastery 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Linux Command Line & Bash Scripting Mastery 강의에는 총 4개의 강의가 포함되어 있습니다.

배열을 함수에 전달하기 어려운 이유

Bash에서 배열과 연관 배열은 일급 값이 아닙니다. 배열을 함수에 전달할 때 $my_array만 간단히 사용할 수는 없습니다. 이렇게 하면 첫 번째 요소만 확장됩니다.

다음과 같이 잘못 시도할 수 있습니다.

my_func "${my_array[@]}"

함수는 단어가 나열된 평면 목록을 받습니다. 어느 요소가 어디에서 끝나고 다음 요소가 어디에서 시작하는지 알 수 없으며, 배열 구조도 완전히 사라집니다.

Bash는 복잡한 데이터 구조를 전달하는 두 가지 신뢰할 수 있는 방법을 제공합니다.

  • 이름 참조(declare -n) — 이름으로 다른 변수를 참조합니다(Bash 4.3 이상).
  • 간접 확장(${!varname[@]}) — 다른 변수에 저장된 이름의 변수에 접근합니다(이전 버전의 Bash).

이 레슨에서는 두 기법을 모두 다루므로 배열과 맵을 사용하는 견고하고 재사용 가능한 함수를 작성할 수 있습니다.

이름 참조로 이름을 사용해 배열 전달하기

현재 가장 깔끔한 방법은 이름 참조를 사용하는 것입니다. 배열의 이름을 문자열로 전달하고, 함수 내부에서 해당 배열을 참조하는 지역 변수를 선언합니다.

핵심 구문은 다음과 같습니다.

  • declare -n ref="$1" — 이제 ref는 첫 번째 인수로 전달된 변수 이름의 별칭입니다.
  • 원래 배열을 다루는 것처럼 ref를 읽고, 순회하고, 수정할 수 있습니다.

이 방법은 배열 전체를 복사하지 않으므로 함수가 어떤 크기의 배열이든 처리할 수 있습니다.

#!/usr/bin/env bash

print_array() {
    declare -n arr="$1"   # nameref: arr -> caller's variable
    echo "Array has ${#arr[@]} elements:"
    for elem in "${arr[@]}"; do
        echo "  - $elem"
    done
}

fruits=(apple banana cherry)
print_array fruits

함수 내부에서 호출자의 배열 수정하기

이름 참조는 실제 별칭이므로 이름 참조를 통한 쓰기 작업은 호출자의 원래 변수를 변경합니다. 값을 다시 출력하거나 전역 변수를 사용할 필요가 없습니다.

따라서 이름 참조는 배열을 제자리에서 변환하는 함수에 적합합니다. 예를 들어 문자열 정규화, 요소 필터링, 정렬 등에 사용할 수 있습니다.

#!/usr/bin/env bash

to_uppercase() {
    declare -n _arr="$1"
    for i in "${!_arr[@]}"; do
        _arr[$i]="${_arr[$i]^^}"   # ^^ = uppercase in Bash 4+
    done
}

words=(hello world bash)
echo "Before: ${words[@]}"
to_uppercase words
echo "After:  ${words[@]}"

이름 참조를 통해 새 배열 반환하기

함수는 출력 매개변수로 호출자가 제공한 배열의 이름을 받아 호출자의 배열에 직접 쓸 수도 있습니다. 호출자가 빈 배열을 만들고 그 이름을 전달하면 함수가 배열을 채웁니다.

이 패턴은 결과 버퍼를 가리키는 포인터를 전달하는 C의 전형적인 관용구와 비슷하지만, 여기서는 모든 작업이 셸 내부에서 이루어집니다.

#!/usr/bin/env bash

get_even_numbers() {
    local -a source=("$@")
    # Last argument is the output array name — pop it off
    local out_name="${source[-1]}"
    unset 'source[-1]'

    declare -n _out="$out_name"
    _out=()   # clear
    for n in "${source[@]}"; do
        (( n % 2 == 0 )) && _out+=("$n")
    done
}

declare -a evens
get_even_numbers 1 2 3 4 5 6 7 8 evens
echo "Evens: ${evens[@]}"

이름 참조 충돌의 함정

이름 참조에는 중요한 함정이 하나 있습니다. 지역 이름 참조 변수의 이름이 호출자의 변수 이름과 같으면 Bash가 무한 자기 참조 루프에 빠져 스크립트가 실패합니다.

예를 들어 호출자가 배열 이름을 arr로 지정했는데 함수에서 declare -n arr="$1"을 실행하면 이름 참조 arr가 자기 자신을 가리킵니다.

이를 피하기 위한 규칙: 내부 이름 참조 변수 앞에 밑줄을 붙이십시오(예: _arr, _ref, _out). 호출자는 이런 이름을 거의 사용하지 않으므로 충돌이 매우 드뭅니다.

#!/usr/bin/env bash

# DANGER: caller uses variable named 'arr'
arr=(one two three)

bad_func() {
    declare -n arr="$1"   # collision! arr refers to itself
    echo "${arr[@]}"
}

good_func() {
    declare -n _arr="$1"  # underscore prefix avoids collision
    echo "${_arr[@]}"
}

# bad_func arr   # would hang / error
good_func arr

이름을 사용해 연관 배열(맵) 전달하기

연관 배열(declare -A)도 이름 참조와 동일하게 작동합니다. 맵의 이름을 문자열로 전달하고 함수 내부에서 이름 참조를 선언하면 모든 키-값 쌍을 읽고 쓸 수 있습니다.

이는 라이브러리 함수 간에 공유해야 하는 구성 맵, 옵션 분석기, 조회 테이블에 매우 유용합니다.

#!/usr/bin/env bash

print_map() {
    declare -n _map="$1"
    echo "Map contents:"
    for key in "${!_map[@]}"; do
        printf '  %-15s => %s\n' "$key" "${_map[$key]}"
    done
}

declare -A config
config[host]="localhost"
config[port]="5432"
config[db]="myapp"

print_map config

이름 참조로 두 맵 병합하기

이름 참조는 함수에 실시간 별칭을 제공하므로 맵 병합과 같은 유틸리티를 깔끔하게 구현할 수 있습니다. 대상 맵과 하나 이상의 원본 맵을 이름으로 전달하면 함수가 원본의 키를 순회하며 대상 맵에 값을 씁니다.

이름 참조를 사용하면 스크립트의 모든 연관 배열에서 작동하는 하나의 범용 merge_map 함수를 작성할 수 있습니다.

#!/usr/bin/env bash

merge_map() {
    # merge_map dest src
    declare -n _dest="$1"
    declare -n _src="$2"
    for key in "${!_src[@]}"; do
        _dest["$key"]="${_src[$key]}"
    done
}

declare -A defaults=([timeout]=30 [retries]=3 [verbose]=false)
declare -A overrides=([retries]=5 [log_level]=debug)

merge_map defaults overrides

for k in "${!defaults[@]}"; do
    echo "$k = ${defaults[$k]}"
done

간접 확장: 이전 Bash와의 호환성

Bash 4.0~4.2(또는 이전 버전의 macOS 기본 셸)를 지원해야 한다면 declare -n을 사용할 수 없습니다. 이때의 대안은 ${!varname}을 사용하는 간접 확장입니다.

간접 확장은 varname의 값을 변수 이름으로 해석하고 해당 변수의 값을 반환합니다. 배열에서는 특수 구문과 함께 사용하여 모든 요소를 가져옵니다.

이름 참조와 비교한 제한 사항은 다음과 같습니다.

  • 실제로는 읽기 전용입니다(값을 다시 쓰려면 eval이 필요하지만 주입 위험이 있습니다).
  • eval 없이는 연관 배열의 키를 안전하게 순회할 수 없습니다.
  • 가능한 경우 이름 참조를 사용하고, 꼭 필요한 경우에만 간접 확장을 대안으로 사용하십시오.
#!/usr/bin/env bash

sum_array() {
    local arr_name="$1"
    # Indirect: build a temporary copy by expanding arr_name[@]
    local -a items
    eval "items=( \"\${${arr_name}[@]}\" )"
    local total=0
    for n in "${items[@]}"; do
        (( total += n ))
    done
    echo "$total"
}

numbers=(10 20 30 40)
result=$(sum_array numbers)
echo "Sum: $result"

모듈식 스크립트 라이브러리 만들기

전문적인 Bash 프로젝트에서는 유틸리티 함수를 라이브러리 파일(예: lib/array_utils.sh)에 저장하고, 필요한 스크립트에서 불러옵니다.

패턴은 다음과 같습니다.

  • 각 라이브러리 파일은 함수만 정의하며 최상위 수준의 부작용은 만들지 않습니다.
  • 센티널 변수로 중복 불러오기를 방지합니다: [[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1
  • 사용하는 스크립트는 source 또는 .을 사용해 라이브러리를 불러옵니다.
  • 함수는 이름 참조를 통해 복잡한 데이터를 주고받으므로 모든 로직이 자체적으로 완결됩니다.

이렇게 하면 일회성 셸 요령이 아니라 테스트 가능하고 재사용 가능하며 유지 관리하기 쉬운 스크립트를 만들 수 있습니다.

실전 예제: 맵을 반환하는 옵션 분석기

실제 활용 사례로, 호출자가 제공한 연관 배열을 채우는 명령줄 옵션 분석기를 살펴보겠습니다. 호출자는 배열 이름을 첫 번째 인수로 전달하고, 그 뒤에 실제 "$@" 인수를 전달합니다.

이 패턴을 사용하면 코드를 복사하거나 전역 변수에 의존하지 않고 어떤 스크립트에서든 같은 분석기를 재사용할 수 있습니다.

#!/usr/bin/env bash

parse_opts() {
    declare -n _opts="$1"
    shift
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --host)   _opts[host]="$2";   shift 2 ;;
            --port)   _opts[port]="$2";   shift 2 ;;
            --user)   _opts[user]="$2";   shift 2 ;;
            *)        echo "Unknown option: $1" >&2; shift ;;
        esac
    done
}

declare -A opts=([host]=localhost [port]=5432 [user]=admin)
parse_opts opts --host db.example.com --port 3306

echo "Connecting to ${opts[host]}:${opts[port]} as ${opts[user]}"

하나의 함수에서 입력 및 출력 이름 참조 함께 사용하기

함수는 여러 이름 참조를 동시에 보유할 수 있습니다. 하나는 입력용이고 다른 하나는 출력용입니다. 이를 통해 순수 변환 함수를 만들 수 있습니다. 즉, 전역 상태를 건드리지 않고 원본 구조에서 읽고 결과를 계산한 뒤 대상 구조에 쓸 수 있습니다.

각 이름 참조에 서로 다른 밑줄 접두사 이름을 사용하면 호출자 변수의 이름이 단순하더라도 충돌을 방지할 수 있습니다.

#!/usr/bin/env bash

filter_map_by_key_prefix() {
    # Usage: filter_map_by_key_prefix src_map dest_map prefix
    declare -n _src="$1"
    declare -n _dst="$2"
    local prefix="$3"

    _dst=()
    for key in "${!_src[@]}"; do
        if [[ $key == ${prefix}* ]]; then
            _dst["$key"]="${_src[$key]}"
        fi
    done
}

declare -A settings=(
    [db_host]=localhost [db_port]=5432
    [cache_ttl]=300     [cache_driver]=redis
    [app_debug]=true
)
declare -A db_settings

filter_map_by_key_prefix settings db_settings db_

for k in "${!db_settings[@]}"; do
    echo "$k = ${db_settings[$k]}"
done

이해도 확인: 이름 참조와 배열 전달

Bash에서 함수 간에 배열과 맵을 전달하는 방법을 얼마나 이해했는지 확인해 보십시오.

복습: Bash에서 복잡한 데이터 구조 전달하기

이 레슨에서 배운 내용은 다음과 같습니다.

  • 배열은 값으로 전달할 수 없습니다. 대신 변수 이름을 문자열로 전달하십시오.
  • declare -n ref="$varname"는 이름 참조를 만듭니다. 이는 호출자의 변수에 대한 실시간 별칭입니다. 읽기와 쓰기 모두 원래 변수에 반영됩니다. Bash 4.3 이상이 필요합니다.
  • 이름 참조 충돌은 내부 변수와 외부 변수의 이름이 같을 때 발생합니다. 내부 이름 참조 앞에 접두사를 붙여 방지하십시오(예: _arr, _map).
  • 출력 매개변수는 비어 있는 결과 배열 또는 맵의 이름을 전달하는 방식으로 작동하며, 함수가 이름 참조를 통해 이를 채웁니다.
  • 하나의 함수에서 여러 이름 참조를 사용하면 전역 부작용 없이 순수한 입력-출력 변환을 수행할 수 있습니다.
  • 간접 확장(${!varname[@]} + eval)은 Bash < 4.3에서 사용하는 이전 방식의 대안이지만, 주입 위험이 있고 실제로는 읽기 전용입니다.
  • 이 기법들을 불러온 라이브러리 파일과 결합하면 모듈식이고 재사용 가능하며 테스트 가능한 Bash 코드 기반을 만들 수 있습니다.

자주 묻는 질문

“함수 간 배열과 연관 맵 전달하기” 강의는 무료인가요?

네 — “함수 간 배열과 연관 맵 전달하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Linux Command Line & Bash Scripting Mastery 강의 전체를 잠금 해제할 수 있습니다. Linux Command Line & Bash Scripting Mastery 강의에는 총 4개의 강의가 포함되어 있습니다.

“함수 간 배열과 연관 맵 전달하기”에서 뭘 배우나요?

nameref와 간접 확장을 사용하여 복잡한 데이터 구조를 함수 안팎으로 안전하게 전달하는 방법을 배웁니다. 브라우저에서 직접 실행하는 실습 코드로 Linux Command Line & Bash Scripting Mastery을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Linux Command Line & Bash Scripting Mastery을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Linux Command Line & Bash Scripting Mastery은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“함수 간 배열과 연관 맵 전달하기” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Linux Command Line & Bash Scripting Mastery 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Linux Command Line & Bash Scripting Mastery 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 로컬 범위와 반환 코드로 함수 설계하기
  2. 재사용 가능한 Bash 라이브러리 구축과 불러오기
  3. getopts로 플래그와 인수 파싱하기
  4. 함수 간 배열과 연관 맵 전달하기
← Linux Command Line & Bash Scripting Mastery(으)로 돌아가기