linux에서 쉘 스크립트를 작성하고 명령어를 통해 해당 스크립트를 실행하는 방법에는 2가지가 있다.
그렇다면 두 명령어에는 어떤 차이가 있는지 궁금해서 찾아보게 되었다.
아래는 source command에 관한 description이다.
Execute commands from a file in the current shell.
Read and execute commands from FILENAME in the current shell. The
entries in $PATH are used to find the directory containing FILENAME.
If any ARGUMENTS are supplied, they become the positional parameters
when FILENAME is executed.
Exit Status:
Returns the status of the last command executed in FILENAME; fails if
FILENAME cannot be read.
위 내용을 살펴보면 source command는 아래와 같은 특징을 가지고 있다.
위에서 $PATH라는 환경 변수를 통해 Command File을 찾는다고 했다. Command가 실행되는 과정을 $PATH 중심으로 확인해보려고 한다.
실제로 $PATH 환경변수를 확인해보았다.
echo $PATH
/root/.nvm/versions/node/v20.11.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:
/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:...
위와 같이 출력되며 :를 통해 디렉토리를 구분한다. 출력된 내용을 보면 /usr/bin이라는 디렉토리가 있다. echo 명령어의 위치를 찾아보면 아래와 같이 출력된다.
which -p echo
/usr/bin/echo
echo 명령어는 $PATH 환경변수에 포함된 /usr/bin 폴더에 존재하기에 실행할 수 있는 것이다.
root 디렉토리의 하위 폴더인 scripts에 test.sh를 만든 뒤 home 디렉토리에서 실행하면
source test.sh
source: no such file or directory: test.sh
처럼 찾을 수 없다는 메세지가 뜬다. 이때, $PATH의 환경변수에 일시적으로 test.sh가 들어있는 ~/scripts 폴더를 추가시키고 test.sh를 실행시키면
export PATH=$PATH:~/scripts
source test.sh
hello
test.sh의 내용인 echo hello가 제대로 실행되는 것을 확인할 수 있다.
위에서 source command는 현재 쉘에서 실행된다는 점을 알 수 있었다. 그렇다면 쉘이 여러 개일 수도 있는 것일까?
GNU Operating System의 설명에 따르면 shell은 명령을 실행하는 macro processor라고 설명하고 있다. 즉, shell은 process의 일종이라는 것을 의미한다.
이제 source를 통해 실행되는 쉘 스크립트의 PID와 shell 정보를 확인해보자.
test.sh
ps -f -p $$
readlink -f /proc/$$/exe
ps -f -p $$ && readlink -f /proc/$$/exe && source test.sh
#UID PID PPID C STIME TTY TIME CMD
#root 656 655 0 21:14 pts/0 00:00:02 -zsh
#/usr/bin/zsh
#UID PID PPID C STIME TTY TIME CMD
#root 656 655 0 21:14 pts/0 00:00:02 -zsh
#/usr/bin/zsh
위의 코드는 실행 중인 process의 정보와 함께 symbolic Link를 이용해 현재 실행 중인 shell의 절대 위치를 출력하는 코드를 작성했다. PID와 Parent PID, shell의 위치가 동일한 것을 알 수 있다.
source는 같은 쉘에서 실행되기에 파일이 반환한 결과가 해당 쉘에 반영된다. $PATH 환경 변수를 수정한 뒤 생기는 변화를 관찰해보겠다.
#이전 $PATH 환경 변수
... :/mnt/c/Python312:/mnt/c/Users/earth/AppData/Roaming/npm
#home에서 test.sh 실행
source test.sh
#source: no such file or directory: scripts
현재 $PATH의 환경변수에 /root/scripts가 없어 home에서 test.sh가 실행되지 않는다.
#test.sh 내용
export PATH=$PATH:~/scripts
#test.sh 실행
source ~/scripts/test.sh
#환경 변수 확인
echo $PATH
... :/mnt/c/Python312:/mnt/c/Users/earth/AppData/Roaming/npm:/root/scripts
#home에서 test.sh 실행
source test.sh
#에러 코드 없음
test.sh를 source 통해 실행시킴으로써 $PATH에 /root/scripts를 추가했으며 home에서 test.sh를 실행하니 제대로 실행된다.
즉, source는 현재 쉘과 같은 쉘에서 실행되기에 반환된 결과가 해당 쉘에 반영되는 모습을 보여준다.
아래는 GNU.org에서 bash command에 대한 description 중 일부를 발췌한 내용이다.
Shells may be used interactively or non-interactively. In interactive mode,
they accept input typed from the keyboard. When executing non-
interactively, shells execute commands read from a file.
if filename is an executable shell script. This subshell reinitializes itself,
so that the effect is as if a new shell had been invoked to interpret the
script, with the exception that the locations of commands remembered by the
parent (see the description of hash in Bourne Shell Builtins) are retained by the child.
bash 명령어의 특징은 아래와 같다.
#interactive mode
$ ps -f -p $$
#UID PID PPID C STIME TTY TIME CMD
#root 37850 37845 0 16:47 pts/3 00:00:01 -zsh
$ bash -i
root$ echo $ps -f -p $$
#UID PID PPID C STIME TTY TIME CMD
#root 48969 37850 0 17:32 pts/3 00:00:00 bash -i
#non-interactive mode
$ bash test.sh
interactive mode의 경우 non-interactive mode와 동일하게 새로운 쉘이 생성되지만 사용자와 상호작용이 가능한 쉘이 생성된다. non-interactive의 경우 쉘 스크립트의 내용만이 실행된다.
#test.sh
ps -f -p $$
readlink -f /proc/$$/exe
ps -f -p $$ && readlink -f /proc/$$/exe && bash test.sh
#UID PID PPID C STIME TTY TIME CMD
#root 615 614 0 14:16 pts/0 00:00:01 -zsh
#/usr/bin/zsh
#UID PID PPID C STIME TTY TIME CMD
#root 27671 615 0 16:07 pts/0 00:00:00 bash test.sh
#/usr/bin/bash
앞서 살펴본 Source와는 달리 참조하는 shell이 다르며 bash가 zsh를 parent shell로 가지고 있음을 확인할 수 있다.
그렇다면 독립된 shell(자식 shell)에서 반환된 결과가 parent shell에 영향을 미치는지 확인해보자.
#이전 $PATH 환경 변수
... :/mnt/c/Python312:/mnt/c/Users/earth/AppData/Roaming/npm
#home에서 test.sh 실행
source test.sh
#source: no such file or directory: scripts
위에서와 마찬가지로 현재 $PATH의 환경변수에 /root/scripts가 없어 home에서 test.sh가 실행되지 않는다.
#test.sh
export PATH=$PATH:~/scripts
#test.sh 실행
bash ~/scripts/test.sh
#환경 변수 확인
echo $PATH
... :/mnt/c/Python312:/mnt/c/Users/earth/AppData/Roaming/npm
#home에서 test.sh 실행
bash test.sh
#bash: test.sh: No such file or directory
parent shell의 환경변수에는 변화가 없었으며 home에서의 test.sh 또한 실행되지 않았다.
즉, bash 명령어를 통해 생성된 subshell은 parent shell과 독립적인 환경이다.
위에서 살펴봤을 때 두 명령어 간의 가장 큰 차이는 같은 쉘을 사용하느냐의 유무이다. source는 같은 쉘을 통한 단일 프로세스를 사용하기 때문에 해당 shell의 context를 공유한다. 즉, shell의 환경설정 혹은 새로운 변수, 함수를 추가하는 데 용이하다.
반면 bash는 subshell이라는 새로운 프로세스를 생성하기 때문에 독립적인 실행 context를 가진다. 따라서 독립적인 스크립트를 실행하거나 테스트 환경이 필요할 때 주로 사용된다.
이를 표로 정리하면 아래와 같다.
| 특성 | source | bash |
|---|---|---|
| 실행 환경 | 현재 셸 환경에서 실행 | 새로운 하위 셸을 생성하여 실행 |
| 변수 영향 | 현재 셸의 환경 변수가 수정됨 | 부모 셸의 환경에 영향을 주지 않음 |
| 스크립트 종료 후 | 변경된 환경이 유지됨 | 변경사항이 하위 셸과 함께 소멸됨 |
| 주요 용도 | 환경 설정 파일 로드(.bashrc, .profile 등) 현재 셸에 변수/함수 추가 | 독립적인 스크립트 실행 격리된 환경에서의 테스트 |
| 메모리 사용 | 추가 프로세스 생성 없음 | 새로운 프로세스 생성 |
| 실행 속도 | 상대적으로 빠름 | source보다 느림 (프로세스 생성 필요) |
위의 내용을 종합하면 shell은 컴퓨터와 사용자를 연결하는 하나의 process를 의미한다. 이 process는 명령어를 해석하는 역할을 수행할 뿐 아니라 환경 변수를 통해 하나의 context를 구축할 수 있으며 독립적인 프로세스 환경을 통해 안전한 실행 환경을 제공한다. 자식과 부모의 계층 구조를 가진다. 또한 스크립트를 통한 자동화로 사용자를 위한 인터페이스도 제공하는 역할을 수행한다.
참고문헌
https://www.gnu.org/software/bash/manual/bash.html
https://linuxcommand.org/lc3_man_page_index.php#other