로컬 범위와 반환 코드로 함수 설계하기
취약한 전역 변수 대신 로컬 변수, 종료 상태 및 printf 기반 반환 값을 사용하는 함수를 작성하는 방법을 배웁니다.
로컬 범위와 반환 코드로 함수 설계하기은(는) CoddyKit의 무료 DevOps Bootcamp 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DevOps Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
함수 범위가 중요한 이유
Bash에서는 기본적으로 변수가 전역입니다. 함수 내부에서 설정한 변수는 local로 명시적으로 선언하지 않으면 함수를 호출한 쪽의 범위로 유출됩니다. 이는 셸 스크립트에서 미묘한 오류를 일으키는 흔한 원인입니다.
local없이 만든 함수는 호출한 쪽의 변수를 조용히 덮어쓸 수 있습니다.local변수는 함수가 반환될 때 삭제됩니다.- 명확한 범위 경계를 설정하면 함수를 독립적으로 재사용하고 테스트하기 쉬워집니다.
이 레슨에서는 독립적으로 동작하는 함수를 작성하는 방법을 배웁니다. 함수는 local 변수를 사용하고, 종료 코드와 printf로 결과를 전달하며, 암묵적인 전역 상태에 의존하지 않습니다.
전역 변수 누수 문제
다음은 전역 변수 누수의 구체적인 예입니다. set_name 함수가 result라는 변수를 설정하면서 호출자가 사용하던 result 변수를 조용히 덮어씁니다.
이 스크립트를 실행하고 예상치 못한 출력을 확인해 보십시오. 함수 호출이 끝나면 호출자의 result가 사라집니다.
#!/usr/bin/env bash
set_name() {
result="Alice" # No 'local' — this is GLOBAL
}
result="important data"
echo "Before: $result"
set_name
echo "After: $result" # Prints 'Alice', not 'important data''local'로 지역 변수 선언하기
local 내장 명령은 변수의 범위를 변수를 감싸는 함수와 해당 함수가 호출하는 함수로 제한합니다. 함수 밖에서는 변수가 설정되지 않은 상태가 되거나 이전 값을 유지합니다.
local varname— 값을 할당하지 않고 선언합니다.local varname="value"— 한 단계로 선언하고 값을 할당합니다.local -i count=0— 정수형 지역 변수를 선언합니다.local -r PI=3.14159— 읽기 전용 지역 상수를 선언합니다.
권장 사항: 전역으로 사용해야 하는 특별한 이유가 없다면 함수 안의 모든 변수를 local로 선언하십시오.
#!/usr/bin/env bash
greet() {
local name="$1" # local — safe
local greeting="Hello, ${name}!"
echo "$greeting"
} # 'name' and 'greeting' vanish here
name="global value"
greet "Bob"
echo "name is still: $name" # Prints 'global value'반환값으로 사용하는 종료 코드
Bash 함수는 문자열을 return할 수 없습니다. return은 정수 종료 상태(0~255)만 설정합니다. 일반적으로 다음과 같이 사용합니다.
return 0— 성공return 1(또는 0이 아닌 값) — 실패
호출자는 호출 직후 $?를 통해 종료 상태를 읽거나, if 조건에서 함수를 직접 사용할 수 있습니다. 종료 코드는 함수에서 성공 또는 실패를 알리는 관용적인 방법입니다.
#!/usr/bin/env bash
is_even() {
local -i n="$1"
(( n % 2 == 0 )) # arithmetic command: exits 0 if true, 1 if false
}
for num in 2 3 7 10; do
if is_even "$num"; then
echo "$num is even"
else
echo "$num is odd"
fi
doneprintf로 문자열 결과 전달하기
함수가 문자열 결과를 반환해야 할 때는 표준적으로 표준 출력에 출력한 다음 명령 치환 $()으로 캡처합니다. echo 대신 printf를 사용하는 것이 권장되는 이유는 다음과 같습니다.
printf는 기본적으로 끝에 줄 바꿈을 추가하지 않습니다(\n을 포함한 경우는 제외).printf의 동작은 일관되며 POSIX에 정의되어 있지만, 셸마다echo의 동작은 다릅니다.- 명령 치환은 끝의 줄 바꿈을 제거하므로
printf '%s' "$value"가 정확합니다.
#!/usr/bin/env bash
to_uppercase() {
local input="$1"
printf '%s' "${input^^}" # Bash 4+ parameter expansion
}
word="hello"
upper=$(to_uppercase "$word")
echo "Original: $word"
echo "Upper: $upper"종료 코드와 표준 출력 결과 함께 사용하기
잘 설계된 함수는 동시에 결과를 출력하고(성공 시) 실패를 알릴 수 있습니다(종료 코드 사용). 호출자는 출력 결과를 신뢰하기 전에 종료 코드를 확인하여 처리 방법을 결정합니다.
다음 방식은 실제 Bash 라이브러리에서 폭넓게 사용됩니다.
- 성공 시:
printf로 결과를 출력하고return 0을 실행합니다. - 실패 시: 표준 오류에 진단 정보를 작성하고
return 1을 실행합니다. - 오류를 표준 오류에 작성하면 파이프에 전달되는 표준 출력을 깨끗하게 유지할 수 있습니다.
#!/usr/bin/env bash
divide() {
local -i numerator="$1"
local -i denominator="$2"
if (( denominator == 0 )); then
printf 'Error: division by zero\n' >&2
return 1
fi
printf '%d' $(( numerator / denominator ))
return 0
}
if result=$(divide 20 4); then
echo "20 / 4 = $result"
else
echo "Division failed."
fi
if result=$(divide 10 0); then
echo "10 / 0 = $result"
else
echo "Division failed (caught the error)."
fi'local'로 재귀 함수 보호하기
재귀는 local이 왜 필수적인지 가장 명확하게 보여 주는 예입니다. 재귀 호출이 이루어질 때마다 호출 스택에 있는 각 local 변수의 독립적인 사본이 새로 생성됩니다. local이 없으면 각 호출이 동일한 전역 변수를 덮어써 잘못된 결과를 만듭니다.
아래의 팩토리얼 함수가 안전한 이유는 n과 sub가 각 스택 프레임에서 지역 변수이기 때문입니다.
#!/usr/bin/env bash
factorial() {
local -i n="$1"
local -i sub
if (( n <= 1 )); then
printf '1'
return 0
fi
sub=$(factorial $(( n - 1 )))
printf '%d' $(( n * sub ))
}
for i in 1 2 3 4 5 6; do
echo "${i}! = $(factorial $i)"
donelocal -n(이름 참조)으로 하위 셸 문제 피하기
명령 치환 $()은 하위 셸에서 실행됩니다. 그 안에서 이루어진 변수 할당은 부모 셸에서 보이지 않습니다. 하위 셸을 사용하지 않고 함수가 호출자가 제공한 변수에 값을 쓰도록 하려면 Bash 4.3 이상에서 사용할 수 있는 이름 참조(local -n)를 사용하십시오.
local -n ref="$1"는ref를$1에 저장된 이름의 변수에 대한 별칭으로 만듭니다.- 함수 안에서
ref에 값을 할당하면 호출자의 변수가 직접 변경됩니다. - 구현 세부 정보는 지역적으로 유지하면서 하위 셸을 피할 수 있습니다.
#!/usr/bin/env bash
# Fills caller's array by reference — no subshell needed
read_csv_line() {
local -n _out="$1" # nameref to caller's variable
local line="$2"
local IFS=','
read -ra _out <<< "$line"
}
declare -a fields
read_csv_line fields "alice,30,engineer"
echo "Name: ${fields[0]}"
echo "Age: ${fields[1]}"
echo "Role: ${fields[2]}"작은 함수 라이브러리 만들기
실제 Bash 프로젝트에서는 재사용 가능한 함수를 스크립트가 source(또는 점 연산자 .)로 불러오는 라이브러리 파일로 나눕니다. 좋은 라이브러리 설계 규칙은 다음과 같습니다.
- 라이브러리 함수 안의 모든 변수는
local이어야 합니다. - 라이브러리 함수는 절대
exit하지 않고return하므로 호출자가 계속 실행될 수 있어야 합니다. - 이름 충돌을 피하려면 일관된 네임스페이스 접두사(예:
str_,log_)를 사용합니다. - 표식 변수로 중복 불러오기를 방지합니다.
아래는 이러한 규칙을 따르는 최소한의 문자열 유틸리티 라이브러리입니다.
#!/usr/bin/env bash
# lib/str.sh — string utility library
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1
str_trim() {
local str="$1"
str="${str#"${str%%[![:space:]]*}"}"
str="${str%"${str##*[![:space:]]}"}"
printf '%s' "$str"
}
str_repeat() {
local -i times="$2"
local char="$1"
local -i i
for (( i = 0; i < times; i++ )); do
printf '%s' "$char"
done
}
str_contains() {
local haystack="$1"
local needle="$2"
[[ "$haystack" == *"$needle"* ]]
}
# --- self-test when executed directly ---
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
trimmed=$(str_trim " hello world ")
echo "Trimmed: '${trimmed}'"
str_repeat '-' 20; echo
if str_contains "bash scripting" "script"; then
echo "Contains: yes"
fi
fi함수 안에서 인수 검증하기
인수를 받는 함수는 인수를 초기에 검증하고 잘못된 입력에 대해 특정 종료 코드를 반환해야 합니다. 이를 가드 절 패턴이라고 하며, 빠르고 명확하게 실패하는 방식입니다.
$#으로 인수 개수를 확인합니다.- 어떤 작업이든 수행하기 전에 형식이나 유형을 검증합니다.
- 진단 메시지는 표준 오류에만 출력하고 표준 출력에는 절대 출력하지 않습니다.
- 서로 다른 실패 원인에 호출자가 다르게 대응할 수 있도록 서로 다른 0이 아닌 반환 코드(예: 1 = 잘못된 인수, 2 = 파일을 찾을 수 없음)를 사용합니다.
#!/usr/bin/env bash
file_line_count() {
if (( $# != 1 )); then
printf 'Usage: file_line_count <file>\n' >&2
return 1
fi
local file="$1"
if [[ ! -f "$file" ]]; then
printf 'Error: not a file: %s\n' "$file" >&2
return 2
fi
if [[ ! -r "$file" ]]; then
printf 'Error: cannot read: %s\n' "$file" >&2
return 3
fi
local -i count
count=$(wc -l < "$file")
printf '%d' "$count"
return 0
}
# Test with /etc/hosts (exists on every Linux/macOS system)
if lines=$(file_line_count /etc/hosts); then
echo "/etc/hosts has $lines lines"
else
echo "Failed with exit code: $?"
fi모든 내용 종합하기: 실제 예제
이제 이 수업의 모든 개념이 함께 작동하는 완전한 독립 실행형 스크립트를 살펴보겠습니다.
- 모든 함수에서
local변수를 사용합니다. - 성공과 실패를 알리는 종료 코드를 사용합니다.
- 문자열 결과를 전달할 때
printf를 사용합니다. - 오류는 표준 오류에, 결과는 표준 출력에 작성합니다.
- 인수 검증에 가드 절을 사용합니다.
실행 흐름을 살펴보십시오. parse_version이 데이터를 추출하고, version_ge가 데이터를 비교하며, main이 두 함수를 깔끔하게 사용합니다.
#!/usr/bin/env bash
# Parse a semver string into components via nameref
parse_version() {
local -n _major="$2" _minor="$3" _patch="$4"
local version="$1"
local IFS='.'
local -a parts
read -ra parts <<< "$version"
_major="${parts[0]:-0}"
_minor="${parts[1]:-0}"
_patch="${parts[2]:-0}"
}
# Return 0 if version $1 >= version $2
version_ge() {
local -i maj_a min_a pat_a
local -i maj_b min_b pat_b
parse_version "$1" maj_a min_a pat_a
parse_version "$2" maj_b min_b pat_b
if (( maj_a != maj_b )); then (( maj_a > maj_b ))
elif (( min_a != min_b )); then (( min_a > min_b ))
else (( pat_a >= pat_b ))
fi
}
require_bash_version() {
local required="$1"
local actual="${BASH_VERSION%%(*}"
if version_ge "$actual" "$required"; then
printf 'Bash %s satisfies >= %s\n' "$actual" "$required"
return 0
else
printf 'Error: need Bash >= %s, got %s\n' "$required" "$actual" >&2
return 1
fi
}
main() {
require_bash_version "4.3" || return 1
require_bash_version "99.0" || true # demonstrates failure path
}
main지식 확인: 지역 변수와 반환값
다음 Bash 함수를 살펴보십시오. 호출자에서 문자열 결과를 캡처하는 올바른 방법은 무엇이며, tmp 변수에 대한 설명 중 옳은 것은 무엇입니까?
transform() {
local tmp="${1,,}" # lowercase
printf '%s' "$tmp"
return 0
}복습: 지역 범위와 반환 코드를 사용하는 함수
이 수업에서는 깔끔하고, 조합 가능하며, 안전한 Bash 함수를 작성하는 방법을 배웠습니다.
- 항상 함수 안의 변수에
local을 사용하십시오. 호출자의 범위를 오염시키는 것을 방지할 수 있습니다. - 종료 코드(
return 0/1/N)를 사용하여 성공 또는 실패를 알리십시오. 종료 코드는if,&&,||와 자연스럽게 연동됩니다. - 표준 출력에
printf를 사용하여 문자열 결과를 전달하십시오. 호출자에서는$()으로 결과를 캡처합니다. - 오류는 표준 오류에 작성하십시오. (
>&2) 그러면 데이터 흐름과 파이프에 사용되는 표준 출력을 깨끗하게 유지할 수 있습니다. - 호출자가 제공한 변수에 하위 셸의 부담 없이 값을 써야 할 때
local -n(이름 참조)을 사용하십시오. - 가드 절(인수를 초기에 검증하고 잘못된 입력이 들어오면 즉시 반환)은 함수를 견고하고 자기 설명적으로 만듭니다.
- 라이브러리 파일은 불러와서 사용하고, 네임스페이스 접두사를 사용하며,
exit를 호출하지 않고, 중복 불러오기를 방지해야 합니다.
이러한 패턴을 숙달하면 취약한 일회성 스크립트와 전문적이고 유지 관리하기 쉬운 Bash 코드베이스를 구분할 수 있습니다.
자주 묻는 질문
“로컬 범위와 반환 코드로 함수 설계하기” 강의는 무료인가요?
네 — “로컬 범위와 반환 코드로 함수 설계하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DevOps Bootcamp 강의 전체를 잠금 해제할 수 있습니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
“로컬 범위와 반환 코드로 함수 설계하기”에서 뭘 배우나요?
취약한 전역 변수 대신 로컬 변수, 종료 상태 및 printf 기반 반환 값을 사용하는 함수를 작성하는 방법을 배웁니다. 브라우저에서 직접 실행하는 실습 코드로 DevOps Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
DevOps Bootcamp을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 DevOps Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“로컬 범위와 반환 코드로 함수 설계하기” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 DevOps Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 DevOps Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 로컬 범위와 반환 코드로 함수 설계하기
- 재사용 가능한 Bash 라이브러리 구축과 불러오기
- getopts로 플래그와 인수 파싱하기
- 함수 간 배열과 연관 맵 전달하기