source 명령의 정확한 동작에 대해 공부하다 보니
자연스레 환경변수와 로그인셸에 대해 더 자세히 이해하게 되었다.
반대로 말하면, 환경변수와 로그인셸에 대해 이해해야 비로소 source 명령을 정확히 이해할 수 있어 보인다.
따라서 환경변수와 로그인셸에 대해 정리하고, 마지막에 source 명령에 대해 정리한다.
셸 변수와 환경변수
셸에서 값을 담아두기 위해 사용하는 변수에는 크게 셸 변수와 환경변수가 있다.
셸 변수는 단순히 현재 셸에서 담아두고 사용하는 값으로, 현재 셸을 벗어나면 유효하지 않다.
즉, 셸 변수의 scope는 정확히 해당 셸에서 그친다.
반면에 환경변수는 현재 셸은 물론이고, 현재 셸에서 파생된 자식 프로세스에서도 유효하다.
즉, 환경변수의 scope는 현재 셸과 그 자식 프로세스까지 커버한다.
아래 도식에서는 #1 bash 셸에서 bash 명령을 실행하여 #2 bash 셸을 실행했고, #2 bash 셸에서 myprogram이라는 프로세스를 실행했다.
#2 bash 셸에서 셸 변수와 환경변수 모두 선언한 상황에서, 각 변수가 유효한지 여부를 그린 도식이다.

기본적으로 부모 프로세스인 #1 bash 셸에서는 자식의 변수를 참조할 수 없으며,
환경변수는 자식 프로세스 생성 시 상속시키는 변수다.
로그인셸과 비로그인셸
로그인셸은 사용자가 로그인 했을 때 커널이 제공하는 셸로,
사용자 생성 시 /etc/passwd 파일의 로그인셸 필드에 셸의 종류(bash, zsh 등)가 기록된다.
로그인셸이란 위처럼 셸의 종류를 말한다고 볼 수도 있지만,
정확한 의미는 특별한 셸의 종류가 아니라 셸의 실행 모드이다.
즉, 셸은 로그인 모드로 실행할지 비로그인 모드로 실행할지가 구분되어 있으며, 사용자 로그인 시 /etc/passwd 파일에 기록된 셸을 로그인 모드로 실행하는 것이다.
shopt 명령
현재 셸에 지정된 옵션을 출력/수정하는 명령으로, 셸 실행 후 이 명령을 실행하여 로그인셸 여부를 확인해본다.
$ ssh myserver
# (ssh 접속 성공)
# 접속하자마자 shopt로 옵션 확인
[hyeonwoo@myserver]$ shopt | grep login
login_shell on
# 새로운 자식 프로세스(bash 셸) 실행 후, 그 안에서 다시 shopt로 옵션 확인
[hyeonwoo@myserver]$ bash
[hyeonwoo@myserver]$ shopt | grep login
login_shell off
[hyeonwoo@myserver]$ exit
# 로그인셸 모드로 bash 셸 실행 후, 그 안에서 다시 shopt로 옵션 확인
[hyeonwoo@myserver]$ bash -l
[hyeonwoo@myserver]$ shopt | grep login
login_shell on
ℹ️ shopt와 setopt
zsh은 shopt 명령이 아닌 setopt 명령을 사용하여 확인한다.
shopt나 setopt는 셸 자체에 내장(built in)된 명령으로,
shopt는 bash의 빌트인, setopt는 zsh의 빌트인이다.
로그인셸/비로그인셸을 구분하는 목적
앞서 똑같은 셸을 실행해도 로그인모드를 on/off로 구분하고 있음을 확인했다.
그 이유는 여러가지가 있겠지만 목적은 단순히 '로그인 시에 딱 한번만 필요한 작업을 모두 수행해두고, 이후 셸 실행할 때마다 최소한의 작업만 수행한다'는 것이다.
따라서 로그인모드로 셸을 실행한다는 것은 "모든 환경설정 파일을 읽어서 작업을 수행하겠다" 라는 의미가 된다.
만약 로그인모드를 구분하지 않으면, 자식 셸을 실행할 때마다 불필요한 반복 작업이 일어나게 된다.
profile 파일과 rc 파일
profile 파일과 rc 파일은 셸 실행 시 수행할 선행 작업을 기록해둔 스크립트 파일로,/etc 아래 시스템 기본 파일과 ~ 아래 사용자별 파일이 존재한다.
profile 파일은 로그인셸이 실행할 스크립트이며,
rc 파일은 비로그인셸이 실행할 스크립트이다.
그래서 기본적으로 셸은 로그인모드=on일 때 profile 파일만 읽고, off일 때 rc파일만 읽는다.
즉, 사용자가 시스템에 로그인할 때 실행된 로그인셸은 profile 파일만 읽고, 시스템 내부에서 bash 명령으로 실행된 하위 셸은 rc 파일만 읽는다.
이렇게 profile 파일과 rc 파일은 그 역할을 엄격하게 구분해둔다.
그렇다고 로그인 시 profile 파일만 읽고 rc 파일을 읽지 않는다면, 로그인한 사용자는 rc 파일에 기록된 설정을 수동으로 진행해야 하는 불편함이 생긴다.
그래서 일반적으로 profile 파일 내부에 rc 파일을 읽어들이는 구문이 작성되어 있다.
(.bash_profile 파일 예시)
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
ℹ️
source와.명령
동일한 기능의 명령이지만.명령이 UNIX 계열 시스템의 전통적인 표준이다.
source 명령의 정확한 이해
그동안 환경변수를 조작할 때, 단순히 .bashrc와 같은 rc 파일을 수정한 후 source로 적용한다고만 알고 사용해왔다.
source 명령이 정확히 어떤 명령인지 정확히 알아본다.
앞서 환경변수의 scope를 언급할 때, 부모 프로세스에서는 자식 프로세스가 선언한 변수에 접근할 수 없다고 했다.
여기에 셸 스크립트 실행 방식을 떠올려보면, 보통 bash script.sh과 같이 또 다른 셸을 실행하고 스크립트를 전달하는 방식을 사용한다.
만약 script.sh 내부에 export MY_NAME=hyeonwoo 구문을 작성했다면, 이 스크립트를 위해 실행한 자식 셸에서 MY_NAME 환경변수가 유효하지만 스크립트 실행이 종료되면서 자식 셸이 종료되기 때문에 더이상 MY_NAME 환경변수를 확인할 수 없게 된다.
이 상황을 직접 시연해보면 아래와 같다.
# script.sh 파일에 환경변수 MY_NAME 선언
$ echo "export MY_NAME=hyeonwoo" > script.sh
# 스크립트 실행
$ bash script.sh
# 스크립트 실행 이후, MY_NAME 출력
$ echo $MY_NAME
$사용자가 셸을 이용할 때 MY_NAME 환경변수가 기본적으로 지정되어 있어야 하는 상황이라면,
사용자는 셸 실행 시마다 매번 export MY_NAME=hyeonwoo 명령을 해당 셸에 수동으로 입력해야 한다.
source 명령이 이러한 불편함을 해소할 수 있다.
source 명령의 정확한 동작은 다음과 같다.
- 인자로 전달 받은 스크립트를 실행하고, 실행 결과를 현재 셸에 적용한다.
- 이 때 현재 셸이란 사용자가 로그인하면서 생성된 로그인셸이 아닌, source 명령이 직접 실행된 셸을 의미한다.
MY_NAME 환경변수를 source로 적용하는 예시를 다음과 같이 들 수 있다.
# script.sh 파일에 환경변수 MY_NAME 선언
$ echo "export MY_NAME=hyeonwoo" > script.sh
# 하위 셸 실행
$ bash
# 하위 셸에서 source 명령 실행 후 환경변수 확인
$ source script.sh
$ echo $MY_NAME
hyeonwoo
# 하위 셸을 종료하여 기존 셸로 돌아온 후 다시 환경변수 확인
$ exit
$ echo $MY_NAME
$하위 셸에서 source를 실행해서 환경변수 MY_NAME을 선언했다.
하위 셸에서 환경변수가 정상적으로 확인 되지만, 하위 셸 바깥에는 적용되지 않는다.
끝없이 길어지는 환경변수
대부분의 경우 환경변수 선언은 한번만 해두면 된다.
반복해서 선언하면 무의미하게 같은 값으로 오버라이드 될 것이다.
특히 PATH 환경변수에는 여러개의 경로를 콜론(:)으로 구분하여 작성하는데,
이전에 지정했던 중요한 경로를 건드리지 않기 위해
오버라이드 하기보단 append하는 방식을 취한다.
아래 코드는 .bashrc 파일의 일부이다.
PATH 환경변수에 특정 경로가 포함되지 않았을 경우 그 경로를 append한다.
# User specific environment
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]
then
PATH="$HOME/.local/bin:$HOME/bin:$PATH"
fi
export PATH반면에 로컬 맥에 안티그래비티 에디터를 설치하면서 .zshrc에 추가된 구문은 다음과 같다.
# Added by Antigravity
export PATH="/Users/hyeonwoo/.antigravity/antigravity/bin:$PATH".bashrc 처럼 경로 검사는 안하고 무조건 append한다.
이 경우, .zshrc 스크립트를 여러번 실행하면 "/Users/hyeonwoo/.antigravity/antigravity/bin"라는 경로가 PATH 환경변수에 끝없이 중복해서 붙게 된다.
조금 더 간단한 예시로 이러한 케이스를 다시 확인해본다.
먼저 .bashrc 파일에 export MY_NAME="hyeonwoo:$MY_NAME" 구문을 추가하여,
MY_NAME 환경변수의 앞부분에 'hyeonwoo:'를 append한다.
export MY_NAME="hyeonwoo:$MY_NAME"명령을 .bashrc에 추가하지 않고 단순히 셸에서 반복해서 실행하더라도 MY_NAME이 계속 길어지는 것을 볼 수 있다.
.bashrc 파일은 셸이 실행될 때마다 읽어들이고, 덕분에 하위 셸을 실행할 때마다 MY_NAME 환경변수가 길어진다.
하위 셸을 거듭 실행할 수록 길어졌지만, 이는 하위 셸 scope 까지만 유효하므로 exit로 부모 셸로 빠져나갈 수록 다시 짧아지는 것을 확인할 수 있다.
$ echo 'export MY_NAME="hyeonwoo:$MY_NAME"' >> ~/.bashrc
$ echo $MY_NAME
$ bash
$ echo $MY_NAME
hyeonwoo:
$ bash
$ echo $MY_NAME
hyeonwoo:hyeonwoo:
$ bash
$ echo $MY_NAME
hyeonwoo:hyeonwoo:hyeonwoo:
$ exit
$ echo $MY_NAME
hyeonwoo:hyeonwoo:
$ exit
$ echo $MY_NAME
hyeonwoo:
$exit
$ echo $MY_NAME
$헷갈리지 말아야 할 것은
MY_NAME이라는 단 하나의 환경변수가 늘었다 줄었다 하는 것이 아니라,
셸마다 각자의 scope에서 MY_NAME 환경변수를 따로 둔다는 점이다.
'Linux' 카테고리의 다른 글
| [Linux] 디렉터리 권한 파헤치기 (0) | 2026.01.23 |
|---|---|
| [Linux] sudo 파헤치기 (0) | 2026.01.20 |
| 안쓰는 디스크를 리눅스 NFS로 사용하기: Ubuntu 24.04 (0) | 2025.10.07 |
| [Linux] 하드링크 파헤치기 (0) | 2025.09.02 |