프로그래밍이란 컴퓨터가 수행하길 원하는 절차, 즉 루틴(routine)을 정의하는 작업입니다. 간단한 비유로 빵 굽기에 프로그래밍을 대입해 볼 수 있습니다. 재료 목록을 한 번 나열해 작업 환경을 준비한 뒤, 빵 한 덩어리가 완성되기까지 거쳐야 할 단계를 차례로 적어 넣는 방식이죠. 프로그래밍과 베이킹 모두 특정 단계를 서로 다른 시점에 반복해야 하는 경우가 있습니다. 예를 들어 빵을 만들 때는 사워도(sourdough) 배양액에 먹이를 주는 과정이 여기에 해당합니다:
STIR=100
SNOOZE=86400
function feed_culture {
remove_from(pantry)
add(flour, water)
stir($STIR)
sleep($SNOOZE)
}
그리고 나중에는 반죽을 치대고 발효시키는 과정입니다:
KNEAD=600
SNOOZE=7200
function process_dough {
remove_from(proofing_drawer)
knead($KNEAD)
return_to_drawer($SNOOZE)
}
함수란 무엇이며 왜 중요한가
프로그래밍에서 이런 하위 루틴(subroutine)은 함수(function)로 표현됩니다. 함수는 코드의 중복을 줄여 주고, 그만큼 유지보수 부담도 덜어 주기 때문에 개발자에게 필수적인 개념입니다. 예를 들어 빵 굽기를 프로그램으로 구현했다고 상상해 보세요. 반죽 발효 시간을 바꿔야 한다면, 함수를 미리 만들어 두었기 때문에 변수(예제 코드의 SNOOZE) 값이나 반죽 처리 서브루틴 안의 숫자를 딱 한 번만 수정하면 됩니다. 코드베이스 전체에서 반죽과 관련된 부분을 일일이 찾아 다니지 않아도 되고, 하나라도 놓칠 걱정도 없으니 시간이 크게 절약됩니다. 실제로도 값을 하나 변경하지 못해 생긴 버그나, 모든 항목을 한 번에 잡으려다 잘못 실행된 sed 명령 때문에 생긴 버그가 허다합니다.
Bash에서 함수 정의하기
Bash에서 함수를 정의하는 것은 아주 간단합니다. 작성 중인 스크립트 파일 안에 직접 정의하거나, 별도의 파일에 저장하면 됩니다. 함수를 전용 파일로 관리하면 C/C++의 include나 파이썬의 import처럼 source 명령으로 스크립트에 불러와 재사용할 수 있습니다. Bash 함수는 function 키워드로 만듭니다:
function foo {
# code here
}
참고로 foo() { ... }처럼 function 키워드 없이 괄호만으로 정의하는 방식도 널리 쓰이며, 두 방식은 사실상 동일하게 동작합니다.
다음은 인자(argument)와 함께 함수가 동작하는 방식을 보여주는 간단한 예제입니다:
#!/usr/bin/env bash
ARG=$1
function mimic {
if [[ -z $ARG ]]; then
ARG='world'
fi
echo "hello $ARG"
}
mimic $ARG
실행 결과는 다음과 같습니다:
$ ./mimic
hello world
$ ./mimic everybody
hello everybody
스크립트의 마지막 줄이 함수를 실행하는 부분이라는 점에 주목하세요. 이는 스크립트 입문자들이 흔히 혼동하는 부분입니다. 함수는 자동으로 실행되지 않으며, 호출되기 전까지는 잠재적인 루틴으로 존재할 뿐입니다.
함수를 호출하는 줄이 없다면 함수는 정의만 될 뿐, 결코 실행되지 않습니다.
Bash가 처음이라면 예제 스크립트를 마지막 줄을 포함한 상태로 한 번, 그 줄을 주석 처리한 상태로 한 번 실행해 보면서 차이를 직접 확인해 보세요.
함수 활용하기
함수는 간단한 스크립트를 작성할 때도 중요한 프로그래밍 개념입니다. 함수에 익숙해질수록, 명령어를 나열하는 선언적 방식보다 유연한 처리가 필요한 복잡한 문제를 만났을 때 훨씬 수월하게 대응할 수 있습니다. 또한 범용 함수를 별도 파일로 관리하면 자주 사용하는 루틴을 축적할 수 있고, 이를 프로젝트 간에 재활용해 작업량도 줄일 수 있습니다. 평소의 스크립팅 습관을 돌아보고 어디에 함수를 적용하면 좋을지 살펴보시기 바랍니다.