함수는 모든 프로그래밍 언어에서 중요한 역할을 합니다. bash 역시 다른 프로그래밍 언어처럼 함수를 지원하지만, 다른 언어에 비해 구현 방식이 다소 제한적입니다. 그럼에도 불구하고 함수를 잘 활용하면 스크립트의 재사용성과 가독성을 크게 높일 수 있습니다.
함수란 무엇인가?
프로그래밍에서 함수(function)란 프로그램 내에서 특정 작업을 수행하도록 이름이 붙은 코드 블록을 의미합니다. 이 관점에서 함수는 일종의 프로시저(procedure) 또는 루틴(routine)이라고 볼 수 있습니다. 함수가 호출되면 프로그램은 현재 실행 중인 코드 위치를 벗어나 함수 내부의 첫 번째 줄부터 실행을 시작합니다.
예를 들어, 하나의 프로그램에서 여러 지점마다 어떤 숫자의 팩토리얼(계승)을 계산해야 한다고 가정해 봅시다. 매번 팩토리얼을 계산하는 전체 코드를 반복해서 작성하는 대신, 해당 코드를 한 번만 함수 블록 안에 작성해 두고 필요할 때마다 호출하여 재사용할 수 있습니다.
왜 함수를 작성해야 할까?
- 코드를 재사용할 수 있어 중복 작성을 줄여줍니다.
- 프로그램의 가독성을 향상시킵니다.
- 프로그램 내부의 변수를 효율적으로 사용할 수 있습니다.
- 프로그램을 부분별로 나누어 테스트할 수 있습니다.
- 전체 프로그램을 여러 하위 단계의 집합으로 보기 쉽게 표현합니다.
셸 스크립트에서의 함수
셸 스크립트에서 함수를 작성하는 일반적인 문법은 아래와 같이 두 가지 방식이 있습니다.
function func_name {
. . .
commands
. . .
}
또는
func_name ( ) {
. . .
commands
. . .
}
두 번째 방식에서는 여는 중괄호를 다음 줄에 배치하는 것도 가능합니다.
func_name ( )
{
. . .
commands
. . .
}
함수 블록 안에는 평소 셸 스크립트에서 사용하듯 유효한 명령어를 자유롭게 작성할 수 있습니다. 이제 간단한 함수를 포함한 스크립트를 직접 작성해 보겠습니다.
#!/bin/bash
call_echo ( ) {
echo 'This is inside function'
}
op=$1
if [ $# -ne 1 ]; then
echo "Usage: $0 <1/0>"
else
if [ $1 = 0 ] ; then
echo 'This is outside function'
elif [ $1 = 1 ] ; then
call_echo
else
echo 'Invalid argument'
fi
fi
exit 0
위 예제에서 알 수 있듯이, 함수 정의는 반드시 첫 번째 호출보다 앞서 위치해야 합니다. 셸 스크립트에는 다른 언어처럼 '함수 선언'이라는 개념이 없으며, 또한 함수 안에 함수를 중첩(nesting)하는 것도 가능합니다.
주의: 아무 내용도 없는 빈 함수를 작성하면 항상 문법 오류(syntax error)가 발생합니다.
같은 이름의 함수가 여러 번 정의되면, 마지막에 정의된 버전이 호출됩니다. 다음 예제로 확인해 보겠습니다.
#!/bin/bash
func_same ( ) {
echo 'First definition'
}
func_same ( ) {
echo 'Second definition'
}
func_same
exit 0
이 스크립트를 실행하면 'Second definition'이 출력됩니다. 즉, 나중에 정의된 함수가 이전 정의를 덮어쓰게 됩니다.
매개변수 전달과 값 반환하기
이제 한 단계 더 나아가, 매개변수를 받고 값을 반환하는 함수를 살펴보겠습니다. 함수에서 값을 반환하려면 셸 내장 명령인 return을 사용합니다. 문법은 다음과 같습니다.
func_name ( ) {
. . .
commands
. . .
return $ret_val
}
마찬가지로 함수에 인자를 전달할 때는 아래와 같이 공백으로 구분하여 나열하면 됩니다.
func_name $arg_1 $arg_2 $arg_3
함수 내부에서는 전달받은 인자를 순서대로 $1, $2, $3 형태로 접근할 수 있습니다. 두 정수 중 큰 값을 찾는 함수 예제를 통해 개념을 더 명확히 이해해 보겠습니다.
#!/bin/bash
USG_ERR=7
max_two ( ) {
if [ "$1" -eq "$2" ] ; then
echo 'Equal'
exit 0
elif [ "$1" -gt "$2" ] ; then
echo $1
else
echo $2
fi
}
err_str ( ) {
echo "Usage: $0 <number1> <number2>"
exit $USG_ERR
}
NUM_1=$1
NUM_2=$2
if [ $# -ne 2 ] ; then
err_str
elif [ `expr $NUM_1 : '[0-9]*'` -eq ${#NUM_1} ] ; then
if [ `expr $NUM_2 : '[0-9]*'` -eq ${#NUM_2} ] ; then
max_two $NUM_1 $NUM_2
else
err_str
fi
else
err_str
fi
exit 0
스크립트가 다소 복잡해 보이지만, 한 줄씩 읽어 보면 간단합니다. 먼저 중첩된 if-else 문으로 인자의 개수와 타입을 정규표현식을 통해 검증합니다. 이후 두 개의 커맨드라인 인자를 함수에 전달하고 결과를 출력합니다. 결과를 함수 내부에서 바로 출력하는 이유는, 함수가 큰 정수를 반환(return)할 수 없기 때문입니다.
이 문제를 우회하는 또 다른 방법은 전역 변수(global variable)를 사용하여 함수 내부에서 결과를 저장하는 것입니다. 아래 스크립트가 이 방식을 보여줍니다.
#!/bin/bash
USG_ERR=7
ret_val=
max_two ( ) {
if [ "$1" -eq "$2" ] ; then
echo 'Equal'
exit 0
elif [ "$1" -gt "$2" ] ; then
ret_val=$1
else
ret_val=$2
fi
}
err_str ( ) {
echo "Usage: $0 <number1> <number2>"
exit $USG_ERR
}
NUM_1=$1
NUM_2=$2
if [ $# -ne 2 ] ; then
err_str
elif [ `expr $NUM_1 : '[0-9]*'` -eq ${#NUM_1} ] ; then
if [ `expr $NUM_2 : '[0-9]*'` -eq ${#NUM_2} ] ; then
max_two $NUM_1 $NUM_2
echo $ret_val
else
err_str
fi
else
err_str
fi
exit 0
마무리하며
이제 이전 시리즈에서 다룬 흥미로운 문제들을 함수를 활용해 직접 풀어보세요. 시리즈의 앞선 파트들은 아래와 같습니다.
- 리눅스 기본 셸 스크립팅 언어 팁 이해하기 – 파트 I
- 리눅스 초보자를 위한 셸 프로그래밍 학습용 스크립트 5선 – 파트 II
- 리눅스 BASH 스크립팅의 세계 탐험하기 – 파트 III
- 리눅스 셸 프로그래밍의 수학적 측면 – 파트 IV
- 셸 스크립팅 언어로 수학 표현식 계산하기 – 파트 V
다음 파트에서는 지역 변수(local variable) 사용, 재귀(recursion) 등 함수의 고급 기능을 더 자세히 다룰 예정입니다. 많은 기대 부탁드리며, 궁금한 점은 댓글로 남겨주세요.