함수

함수 에서 Bash

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

함수 소개

많은 Bash 스크립트는 엄격한 명령형 스타일로 작성돼요. 명령어를 하나 실행하고, 그다음 명령어를 실행하고, 이런 식이죠. 때로는 개념적으로 하나의 목적을 수행하는 명령어들을 한데 묶어야 할 때가 있어요. 이럴 때 필요한 것이 바로 _함수_예요.

함수 정의하기

함수는 이렇게 정의해요:

my_function () {
    COMMANDS
}

비어 있는 괄호는 그냥 함수를 정의하고 있다는 표시일 뿐이에요. 괄호 안에는 아무것도 들어가지 않아요.

함수 매개변수

함수는 한 번 정의하고 나면 다른 명령어(내장이든 아니든)와 똑같이 동작해요. 다른 명령어에 그러듯 함수에도 _인자_를 넘길 수 있어요. 함수 안에서는 _위치 매개변수_인 $1, $2 등을 사용해서 인자에 접근해요. (변수 개념에서 위치 매개변수에 대해 배웠던 걸 떠올려 봐요.)

Advanced

특수 매개변수 $0은 함수 안에서도 바뀌지 않아요. 여전히 실행 중인 스크립트의 이름이에요. 현재 실행 중인 함수는 $FUNCNAME 변수로 자기 이름에 접근할 수 있어요.

매뉴얼의 3.4.2 특수 매개변수를 참고해요.

변수

함수 안에서 변수를 정의할 수 있어요. local 명령어로 변수를 선언하면 그 변수의 _스코프_는 현재 함수(그리고 그 함수가 호출하는 함수들)로 제한돼요. 그렇지 않으면 변수는 _전역 스코프_에 놓여요.

지역 변수는 전역 변수와 같은 이름을 가질 수 있어요. 이때 지역 변수는 전역 변수를 "가려요". 예를 들어 함수에서 선언한 지역 변수는 같은 이름의 전역 변수를 숨겨요. 참조와 대입이 모두 지역 변수를 가리키고, 전역 변수는 그대로 남아 있어요. 함수가 반환하면 전역 변수가 다시 보여요.

x=5

myfunc () {
    local x=100
    echo "in my function, $x == 100"
}

echo "in the global scope, $x == 5"

myfunc

echo "back in the global scope, $x == 5"

출력은 다음과 같아요.

in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5

함수 안에서는 _호출자_의 스코프에 있는 변수에 접근할 수 있어요. 즉 전역 변수뿐 아니라, 호출자(또는 호출자를 호출한 어떤 함수)에서 선언한 지역 변수도 사용할 수 있어요.

Advanced

엄밀히 말하면 "전역"이라는 표현이 정확한 건 아니에요. 함수에서 변수를 확장할 때 Bash는 그 변수 이름이 선언된 함수를 찾기 위해, 전역 스코프에 이르기까지 호출 스택을 거슬러 올라가요.

다음 예제는 매뉴얼의 셸 함수 부분을 각색한 거예요:

func1() {
    local var='func1 local'
    func2
}

func2() {
    echo "In func2, var = $var"
}

var=global
func1
func2

출력은 다음과 같아요:

In func2, var = func1 local
In func2, var = global

마찬가지로 변수에 값을 _대입_하면 그 변수가 선언된 스코프에서 대입돼요. 이런 "멀리서 벌어지는 일"은 코드를 따라가기 어렵게 만들 수 있어요. 변수가 어디에서 값을 대입받았는지 항상 분명한 건 아니니까요.

Advanced

FUNCNAME 배열 변수를 사용하면 호출 스택을 살펴볼 수 있어요.

반환 값

함수는 다른 명령어와 마찬가지로 _종료 상태_를 가져요. 기본적으로 함수의 상태는 _마지막으로 실행된 명령어_의 종료 상태예요.

return 명령어를 사용하면 특정 종료 상태로 함수에서 반환할 수 있어요.

check_password () {
    if [[ $1 == "secret" ]]; then
        return 0
    else
        return 1
    fi
}

read -sp "Enter your password: " pass

if check_password "$pass"; then
    echo "Correct!"
else
    echo "Wrong password."
fi

인자 없이 return을 사용하면 마지막으로 실행된 명령어의 상태를 반환해요.

Note

check_password 함수는 다음과 같이 단순화할 수 있어요:

check_password () { [[ $1 == "secret" ]]; }
  1. [[...]] 조건부 구조는 종료 상태를 가져요: "true"면 0, "false"면 1이에요.
  2. {...} 그룹 구조는 닫는 중괄호 앞에 줄바꿈이나 세미콜론이 있어야 해요.

함수의 출력

함수의 반환 상태는 그냥 숫자예요. 그럼 함수는 어떻게 출력을 만들어 낼까요?

함수는 표준 출력으로 출력할 수 있어요. 익숙한 _명령어 치환_으로 그 출력을 담아 봐요:

d6 () { echo "$(( 1 + RANDOM % 6 ))"; }

die=$( d6 )
echo "You rolled a $die."

출력과 상태를 함께 사용하기

출력을 담는 중에도 함수의 종료 상태를 함께 사용할 수 있어요.

roll () {
    local n=$1
    if (( 4 <= n && n <= 20 )); then
        echo "$(( 1 + RANDOM % n ))"  # exit status is 0
    else
        return 1
    fi
}

read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
    echo "You rolled a $die."
else
    echo "I can't roll a die with $faces faces."
fi

재귀

함수는 자기 자신을 재귀적으로 호출할 수 있어요. 기본적으로 재귀 깊이에는 제한이 없어요.

예를 들어 볼까요:

fibonacci() {
    local n=$1
    if (( n <= 1 )); then
        echo "1"
    else
        local a=$(fibonacci "$(( n - 1 ))")
        local b=$(fibonacci "$(( n - 2 ))")
        echo "$(( a + b ))"
    fi
}

for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
Advanced

FUNCNEST 변수로 재귀 깊이를 제어할 수 있어요.

bash -c '
    recur() {
        echo $1
        recur $(($1 + 1))
    }
    FUNCNEST=5
    recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)
GitHub에서 편집 링크가 새 창이나 탭에서 열려요