지난 글 '셸 스크립트에서 함수 이해하고 작성하기'를 통해 함수를 작성하는 기본적인 방법을 익히셨을 것입니다. 이번 파트에서는 한 단계 더 나아가 지역 변수(local variable)의 사용법과 재귀(recursion)라는 흥미로운 함수 기능을 깊이 있게 살펴보겠습니다.
지역 변수(Local Variables)란?
변수가 지역 변수가 되는 조건은 무엇일까요? 핵심은 변수가 선언된 코드 블록에 있습니다. local 키워드로 선언된 변수는 해당 변수가 선언된 코드 블록 내부에서만 접근할 수 있습니다. 즉, 변수의 유효 범위(scope)가 블록 내부로 한정되는 것입니다. 아래 예제를 통해 자세히 알아보겠습니다.
#!/bin/bash
func( ) {
local i=10
j=20
echo "i from func = $i"
echo "j from func = $j"
}
echo "i outside func = $i"
echo "j outside func = $j"
func
echo "i outside func = $i"
echo "j outside func = $j"
exit 0
위 스크립트를 실행하면 다음과 같은 결과가 출력됩니다.
i outside func = j outside func = i from func = 10 j from func = 20 i outside func = j outside func = 20
처음 두 개의 echo 문이 실행될 때는 함수 func가 아직 호출되지 않았기 때문에 변수 값이 비어 있습니다. 그런데 함수를 호출한 후에는 동일한 echo 문들이 전혀 다른 결과를 출력합니다. 함수 내부에서 local 키워드 없이 선언된 변수 j는 이후에도 계속 접근할 수 있어 값이 20으로 출력된 것입니다.
그렇다면 지역 변수 i는 어떨까요? i의 유효 범위가 함수 func 내부로 한정되어 있기 때문에 외부에서 값 10에 접근할 수 없습니다. 주목할 점은, local 없이 함수 안에서 일반적으로 선언된 변수 j는 기본적으로 전역 변수(global variable)로 취급된다는 사실입니다.
이제 지역 변수의 개념과 함수 블록 내에서의 활용법을 익혔으니, 함수의 가장 흥미로운 주제인 재귀(recursion)로 넘어가 보겠습니다.
재귀(Recursion)란 무엇인가?
함수가 자기 자신을 호출하는 과정을 일반적으로 재귀라고 부릅니다. 또는 '동일한 알고리즘의 더 단순한 버전을 이용해 알고리즘을 표현하는 방식'으로 정의할 수도 있습니다. 대표적인 예로 숫자의 팩토리얼(factorial) 구하기가 있습니다. 팩토리얼은 n! = 1 × 2 × 3 × … × (n-1) × n으로 정의되며, 이를 점화식으로 표현하면 다음과 같습니다.
n! = (n-1)! x n
따라서 같은 함수를 재귀적으로 호출하고, 각 호출의 반환 값을 이전 결과와 곱하면 됩니다.
5! = 4! x 5 4! = 3! x 4 3! = 2! x 3 2! = 1! x 2 1! = 0! x 1
지역 변수를 활용한 재귀
이번에는 지역 변수와 재귀를 함께 사용해 숫자의 팩토리얼을 구하는 스크립트를 작성해 보겠습니다.
#!/bin/bash
fact( ) {
local num=$1
if [ $num -eq 0 ]; then
ret=1
else
temp=$((num-1))
fact $temp
ret=$((num*$?))
fi
return $ret
}
fact 5
echo "Factorial of 5 = $?"
exit 0
num은 호출할 때마다 n-1 값을 저장하는 지역 변수입니다. 기저 조건(base condition)은 숫자가 0인지 검사합니다(0! = 1이며, 음수에 대한 팩토리얼은 정의되지 않기 때문입니다). 이 기저 조건에 도달하면 호출자에게 값 1을 반환합니다. 이 시점에서 num = 1, ret = 1 × 1이 됩니다.
그러면 다시 1을 호출자에게 반환하고, 이번에는 num = 2, ret = 2 × 1이 됩니다. 이런 식으로 반복되다가 마지막에 num = 5일 때 반환 값은 24가 되고, 최종 결과는 ret = 5 × 24로 계산됩니다. 최종 결과 120이 최초 호출문까지 전달되어 화면에 출력됩니다.
다만 위 스크립트에는 한 가지 문제점이 있습니다. 이전 글에서 설명했듯이 셸 함수는 큰 정수를 반환할 수 없습니다. 이 문제에 대한 해결책은 여러분이 직접 고민해 보시기 바랍니다.
Q. 지역 변수 없이도 재귀를 구현할 수 있을까요? 답은 예(Yes)입니다.
지역 변수 없이 재귀 구현하기
다음은 재귀를 이용해 피보나치 수열(Fibonacci series)을 출력하는 예제입니다. 기본 점화식은 다음과 같습니다.
fib(0) = 0
fib(1) = 1
else
fib(n) = fib(n-1) + fib(n-2)
Fibonacci series using recursion
#!/bin/bash
fib( ) {
a=$1
if [ $a -lt 2 ]; then
echo $a
else
((--a))
b=$(fib $a)
((--a))
c=$(fib $a)
echo $((b+c))
fi
}
for i in $(seq 0 15)
do
out=$(fib $i)
echo $out
done
exit 0
위 스크립트에는 지역 변수가 하나도 사용되지 않았습니다. 실행 흐름을 직접 추적해 보면서 이해해 보시기 바랍니다.
여기서 값 15는 출력할 피보나치 수열의 항 개수를 의미합니다. 스크립트를 실행해 보면 눈치챘겠지만, 실행에 꽤 시간이 걸립니다. 셸 스크립트의 재귀는 C 같은 프로그래밍 언어의 재귀보다 속도가 느립니다.
이번 글로 셸 스크립팅의 함수 파트를 마무리하려고 합니다. 앞으로 다룰 배열(array) 등 더 유익한 주제들도 기대해 주세요!