루비에서 커맨드 라인 인자가 ARGV라는 전역 배열에 담긴다는 것은 이미 많이 알려진 사실입니다. 그런데 왜 하필 ARGV라는 이름일까요? 이 이름에는 흥미로운 역사가 숨어 있으며, 루비의 뿌리가 C 언어에 깊이 닿아 있다는 사실을 잘 보여줍니다.
Argument Vector(인자 벡터)
ARGV는 argument vector(인자 벡터)의 약자입니다. 여기서 '벡터(vector)'는 다소 옛스러운 표현으로, '1차원 배열'을 의미합니다.
C 언어에서 모든 프로그램은 main() 함수를 가지며, 일반적으로 다음과 같은 형태를 띱니다.
int main(int argc, char *argv[]) {
... your code here
}
눈치채셨겠지만, main 함수는 두 개의 매개변수를 받습니다. 하나는 커맨드 라인 인자의 개수(count)이고, 다른 하나는 인자들이 담긴 문자열 배열입니다.
bash에 프로그램 실행을 지시하면 시스템 콜(system call)이 발생하고, 운영체제는 해당 프로그램의 main 함수를 호출하면서 미리 준비된 argc와 argv 값을 함께 전달합니다.
Ruby
루비 인터프리터 — 적어도 MRI(Matz's Ruby Interpreter)는 — 그 자체가 하나의 C 프로그램입니다. 실제 루비의 main 함수는 다음과 같습니다.
int
main(int argc, char **argv)
{
#ifdef RUBY_DEBUG_ENV
ruby_set_debug_option(getenv("RUBY_DEBUG"));
#endif
#ifdef HAVE_LOCALE_H
setlocale(LC_CTYPE, "");
#endif
ruby_sysinit(&argc, &argv);
{
RUBY_INIT_STACK;
ruby_init();
return ruby_run_node(ruby_options(argc, argv));
}
}
위 코드에서 볼 수 있듯이, argc와 argv는 ruby_options라는 함수로 전달되고, 이 함수는 다시 ruby_process_options를 호출하며, 그것이 또 process_options를 호출하는 구조로 이어집니다.
이 호출 체인은 루비 인터프리터의 모든 옵션을 처리한 뒤, 최종적으로 ruby_set_argv를 호출하여 우리가 루비 코드에서 사용하는 ARGV를 설정합니다.
void
ruby_set_argv(int argc, char **argv)
{
int i;
VALUE av = rb_argv;
#if defined(USE_DLN_A_OUT)
if (origarg.argv)
dln_argv0 = origarg.argv[0];
else
dln_argv0 = argv[0];
#endif
rb_ary_clear(av);
for (i = 0; i < argc; i++) {
VALUE arg = external_str_new_cstr(argv[i]);
OBJ_FREEZE(arg);
rb_ary_push(av, arg);
}
}
참으로 흥미롭지 않습니까? 아직 MRI 코드베이스를 깊이 탐험하기 시작한 단계이지만, 내부를 직접 들여다보며 동작 원리를 정확히 파악하는 과정은 상당히 재미있는 경험입니다.