이 글의 핵심 목표는 Bash에서 스크립트를 직접 실행하는 것과 source로 불러오는 것 사이에 어떤 차이가 있는지 명확하게 이해하도록 돕는 것입니다. 먼저, 스크립트를 호출하는 다양한 방식에 따라 프로그램이 어떻게 처리되는지부터 살펴보겠습니다.
참고: 스크립트를 만들 때 확장자를 붙이는 것은 필수가 아닙니다. 확장자가 없어도 스크립트는 문제없이 실행됩니다.
쉬뱅(Shebang)이란?
기본적으로 모든 스크립트는 쉬뱅(shebang, #!)이라는 라인으로 시작합니다. Bash에서 #(해시) 기호는 일반적으로 주석으로 해석되지만, shebang은 특별한 의미를 가집니다. shebang은 Bash에게 해당 프로그램을 지정된 인터프리터로 넘겨 실행하도록 지시하는 역할을 합니다.
아래는 간단한 예제 프로그램이며, 인터프리터로 bash를 지정했습니다.
$ cat >> Hello_World.sh #!/usr/bin/env bash echo "Hello world" $ chmod +x Hello_world.sh
스크립트를 실행하는 두 가지 방법
- 상대 경로 사용: 스크립트가 있는 디렉터리로 이동한 후 ./Hello_world.sh를 입력해 실행합니다.
- 절대 경로 사용: 파일 시스템 어디에서든 스크립트의 전체 경로를 입력해 실행합니다.
$ ./Hello_world.sh $ pwd $ /home/karthick/Hello_world
Shebang 없이 실행하면 어떻게 될까?
그렇다면 shebang 없이 프로그램을 실행하면 어떤 일이 벌어질까요? shebang이 없으면 프로그램은 현재 실행 중인 셸, 즉 여기서는 Bash(/bin/bash)로 전달됩니다.
예제를 통해 확인해 보겠습니다. shebang 없이 파이썬 스크립트를 만들고 실행하면, bash는 이 프로그램을 파이썬 인터프리터로 넘겨야 한다는 사실을 알지 못하고 현재 셸에서 그대로 실행해 버립니다.
$ cat > run-py.py
echo $SHELL
print("Hello world")
$ chmod +x run-py.py
$ ./run-py.py

이 경우 프로그램을 실행할 인터프리터를 직접 지정하거나, 항상 권장되는 방법인 shebang 라인을 추가하면 해결됩니다.
# which python3 $(which python3) /home/karthick/run_py.py

포크(Fork): 자식 프로세스 생성
스크립트를 호출하는 방법을 알았으니, 이제 스크립트를 실행했을 때 실제로 무슨 일이 일어나는지 이해해야 합니다. 위 예제처럼 스크립트를 실행하면 새로운 자식 프로세스가 생성되고(forking), 스크립트는 그 자식 프로세스에 전달됩니다. 아래 명령어를 실행하는 간단한 샘플 스크립트를 통해 스크립트가 자식 프로세스로 전달되는 것을 확인할 수 있습니다.
$ ps -ef --forest | grep -i bash

하나의 스크립트가 여러 개의 자식 프로세스를 만들 수도 있으며, 그 수는 코드에 따라 달라집니다. 특히 주목할 점은 서브스크립트(자식)에서 생성된 환경 변수는 스크립트가 종료되면 함께 사라진다는 것입니다. 자식 프로세스는 부모 프로세스가 export한 변수에는 접근할 수 있지만, 반대로 부모 프로세스는 자식 프로세스가 만든 변수에는 접근할 수 없습니다.
변수가 동작하는 방식과 변수 내보내기(export)에 대해 더 깊이 알고 싶다면 아래 글들을 참고하세요.
- 셸 스크립팅에서 '리눅스 변수' 이해하고 작성하기
- Bash에서 $$와 $BASHPID의 차이 배우기
소싱(Sourcing)이란?
"source"는 셸 내장 명령으로, 인자로 전달된 파일을 읽어 현재 셸 환경에서 코드를 실행합니다. 가장 흔한 활용 사례는 .bashrc나 .bash_profile의 설정을 수정한 후 source 명령으로 변경 사항을 다시 로드하는 것입니다.
$ type -a source
source 명령을 실행하는 구문은 두 가지가 있으며, 어느 쪽을 선택할지는 개인 취향에 따라 결정하면 됩니다.
$ source FILE_NAME [ARGUMENTS] $ . FILE_NAME [ARGUMENTS]
소싱 동작 원리 실습
source가 실제로 어떻게 동작하는지 살펴보겠습니다. 두 개의 셸 스크립트를 만들겠습니다. 첫 번째 스크립트(Module.sh)는 변수와 함수를 담고 있고, 두 번째 스크립트(Main.sh)는 그 변수를 출력하고 함수를 호출합니다.
Module.sh 파일:
#!/usr/bin/env bash
VAR1=$(echo "Welcome to $1")
function f1(){
echo “Function f1 is called”
}
Main.sh 파일:
#!/usr/bin/env bash echo $VAR1 f1
스크립트에 실행 권한을 부여하고 메인 스크립트인 main.sh를 실행해 보겠습니다. 이 스크립트는 현재 셸 환경에서 함수 f1과 변수 VAR1을 찾으려 하지만, 해당 요소들이 존재하지 않아 command not found 오류가 발생합니다.
$ bash main.sh
이제 스크립트 안에서 source 명령을 사용해 보겠습니다. source는 변수와 함수를 현재 셸 환경에 로드하므로 main.sh에서도 이를 접근할 수 있게 됩니다.
Module.sh 파일:
#!/usr/bin/env bash
VAR1=$(echo "Welcome to $1")
function f1(){
echo "Function f1 is called"
}
Main.sh 파일:
#!/usr/bin/env bash source module.sh Tecmint echo $VAR1 f1
이제 스크립트를 다시 실행해 결과를 확인해 보세요.
$ bash main.sh
source는 Bash에서 셸 스크립트를 작성할 때 모듈화 프로그래밍 방식을 적용하는 데 매우 유용합니다. 코드를 작은 모듈 단위로 나누어 여러 프로그램에서 재사용할 수 있으며, 이를 통해 DRY(Don't Repeat Yourself) 원칙, 즉 같은 코드를 반복하지 않는 원칙을 지킬 수 있습니다.
이것으로 이 글을 마치겠습니다. 지금까지 Bash에서 소싱과 포크의 차이점을 간략하게 살펴보았습니다. 글을 읽고 소중한 피드백을 공유해 주시기 바랍니다.