본문 바로가기
일반IT/리눅스

export VAR=value와 VAR=value는 무엇이 다를까? 환경변수 전달 범위 한 번에 이해하기

by gasbugs 2026. 9. 1.

터미널에서 NAME=value라고 쓰면 변수가 만들어집니다. 그런데 그다음 실행한 프로그램에서는 값이 보이지 않을 수 있습니다. 반면 export NAME=value로 선언하면 프로그램에서도 값을 읽습니다. 둘의 차이는 값의 모양이 아니라 그 값을 자식 프로세스에게 전달하느냐에 있습니다.

 

가장 먼저 기억할 문장은 이것입니다.

VAR=value는 현재 셸만 쓰는 셸 변수이고, export VAR=value는 이후 실행하는 자식 프로세스에도 전달되는 환경변수다.

여기서 셸(shell)은 Bash나 Zsh처럼 명령을 해석하는 프로그램이고, 프로세스(process)는 실행 중인 프로그램 한 개를 뜻합니다. 터미널의 셸에서 python, node, docker 같은 명령을 실행하면 그 프로그램은 셸의 자식 프로세스로 시작합니다.

현재 셸 안에만 남는 변수와 자식 프로세스에게 전달되는 환경변수

변수는 같지만 전달 표시가 다르다

Bash에서 셸 변수와 환경변수가 완전히 별개의 저장소에 따로 존재한다고 생각하면 오히려 헷갈립니다. 실무적으로는 셸 변수를 만들고, 그 변수에 export 속성을 붙이면 자식에게 전달된다고 이해하는 편이 정확합니다.

PROJECT_MODE=development

이 명령은 현재 셸에 PROJECT_MODE라는 변수를 만듭니다. 같은 셸에서는 값을 읽을 수 있습니다.

printf '%s\n' "$PROJECT_MODE"
# development

하지만 새 Bash를 자식 프로세스로 실행하면 값이 전달되지 않습니다.

bash -c 'printf "%s\n" "${PROJECT_MODE-unset}"'
# unset

이제 기존 변수에 export 속성을 붙여 보겠습니다.

export PROJECT_MODE

bash -c 'printf "%s\n" "$PROJECT_MODE"'
# development

export PROJECT_MODE는 값을 새로 복사하는 명령이라기보다, 앞으로 실행할 명령의 환경에 이 변수를 포함하라는 표시입니다. 다음처럼 값 지정과 export를 한 줄로 합쳐도 결과는 같습니다.

export PROJECT_MODE=development

GNU Bash 공식 문서도 실행되는 명령이 부모 셸의 환경을 상속하며, export가 변수를 이후 명령의 환경에 포함하도록 표시한다고 설명합니다. POSIX 셸 표준 역시 export 속성이 붙은 변수가 이후 실행 명령의 환경에 들어간다고 정의합니다.

부모 셸과 자식 프로세스를 가족관계처럼 보면 쉽다

현재 터미널의 Bash나 Zsh를 부모라고 생각해 봅시다. 이 셸에서 Python을 실행하면 Python은 자식입니다.

터미널의 셸
├── python app.py
├── node server.js
└── bash script.sh

일반 셸 변수는 부모가 자신의 메모장에만 적어 둔 값입니다. export된 변수는 자식이 태어날 때 건네주는 환경 목록에 포함됩니다. 여기서 비유의 중요한 한계가 하나 있습니다. 자식이 받은 값을 바꿔도 부모의 값이 거꾸로 바뀌지는 않습니다.

export COLOR=blue

bash -c 'COLOR=red; printf "child=%s\n" "$COLOR"'
printf 'parent=%s\n' "$COLOR"

# child=red
# parent=blue

프로세스 환경은 기본적으로 부모에서 자식 방향으로 전달됩니다. 자식 프로세스가 부모 셸의 변수 저장소를 직접 수정하는 통로가 아닙니다.

네 가지 선언 방식 비교

작성 방식 현재 셸에서 유지 자식 프로세스에 전달 주된 용도
VAR=value 예 아니요 현재 셸 안의 계산·설정
export VAR=value 예 예 이후 실행할 여러 프로그램의 설정
VAR=value command 아니요 해당 command에만 예 한 명령에만 임시 설정 전달
env VAR=value command 아니요 해당 command에만 예 명시적인 임시 환경 구성

세 번째 방식은 배포와 테스트에서 특히 유용합니다.

APP_ENV=test python app.py

python app.py는 APP_ENV=test를 받지만, 명령이 끝난 뒤 현재 셸에 APP_ENV가 새로 남지는 않습니다.

APP_ENV=test bash -c 'printf "inside=%s\n" "$APP_ENV"'
printf 'after=%s\n' "${APP_ENV-unset}"

# inside=test
# after=unset

GNU Bash 매뉴얼은 명령 앞의 변수 대입이 그 명령이 실행되는 동안만 환경에 추가되며, 현재 셸 환경에는 영향을 주지 않는다고 설명합니다. 셸 함수와 특수 builtin에서는 세부 규칙이 달라질 수 있으므로, 외부 프로그램을 실행하는 일반적인 사례부터 이해하는 것이 좋습니다.

source로 실행할 때는 왜 export 없이도 보일까?

스크립트를 실행하는 방식도 변수의 가시성에 영향을 줍니다.

TOKEN=demo
./check.sh

./check.sh는 별도 자식 프로세스로 실행됩니다. TOKEN을 export하지 않았다면 스크립트는 보통 이 값을 받지 못합니다.

 

반면 다음 명령은 다릅니다.

TOKEN=demo
source ./check.sh

source 또는 점 명령인 . ./check.sh는 새 프로세스를 시작하지 않고 현재 셸 안에서 파일의 명령을 실행합니다. 따라서 현재 셸 변수도 읽을 수 있고, 스크립트가 바꾼 변수도 현재 셸에 남습니다.

 

이 차이 때문에 Python 가상환경을 활성화할 때 ./venv/bin/activate가 아니라 다음처럼 source를 사용합니다.

source .venv/bin/activate

활성화 스크립트가 현재 셸의 PATH를 바꿔야 하기 때문입니다.

export했다고 영구 저장되는 것은 아니다

export를 “영구 환경변수 등록”으로 오해하기 쉽지만, export는 현재 셸 프로세스와 그 자식에게만 적용됩니다. 터미널 창을 닫아 셸이 종료되면 그 설정도 사라집니다.

 

새 셸을 열 때마다 설정하려면 사용하는 셸의 시작 파일에 선언해야 합니다.

# Bash의 대화형 셸에서 흔히 사용하는 파일
export APP_HOME="$HOME/apps/example"
  • Bash에서는 상황에 따라 ~/.bashrc, ~/.bash_profile, ~/.profile 등이 사용됩니다.
  • Zsh의 대화형 설정에는 보통 ~/.zshrc를 사용합니다.
  • 로그인 셸인지, 대화형 셸인지, 스크립트 실행인지에 따라 읽는 파일이 다릅니다.

설정 파일을 수정한 뒤 현재 셸에 즉시 적용하려면 해당 파일을 source할 수 있습니다.

source ~/.zshrc

단, 설정을 여러 파일에 중복 선언하면 어느 파일이 마지막으로 값을 덮어썼는지 추적하기 어려워집니다. 목적에 맞는 파일 한 곳에 두는 것이 좋습니다.

.env 파일은 자동으로 환경변수가 되지 않는다

다음과 같은 .env 파일을 만들었다고 가정해 봅시다.

DATABASE_HOST=localhost
DATABASE_PORT=5432

파일이 존재한다는 사실만으로 운영체제나 셸이 자동으로 읽지는 않습니다. 애플리케이션의 dotenv 라이브러리, Docker Compose, 개발 도구 또는 별도의 셸 코드가 파일을 읽어야 합니다.

 

셸에서 대입문이 들어 있는 파일을 모두 export하고 싶다면 Bash에서 다음 패턴을 볼 수 있습니다.

set -a
source .env
set +a

set -a는 이후 생성하거나 수정하는 변수에 자동으로 export 속성을 붙입니다. 다만 .env가 반드시 안전한 셸 문법이라는 보장은 없습니다. 신뢰할 수 없는 파일을 source하면 그 안의 명령도 현재 셸에서 실행되므로 위험합니다. 단순 데이터 파일은 사용하는 도구의 전용 .env 파서를 이용하는 편이 안전합니다.

값을 확인하는 명령도 서로 다르다

현재 셸 변수까지 폭넓게 보고 싶다면 Bash의 set을 사용할 수 있습니다. 출력량이 많고 함수까지 포함될 수 있으므로 보통 이름을 알고 조회하는 편이 낫습니다.

printf '%s\n' "$PROJECT_MODE"
declare -p PROJECT_MODE

export된 변수 목록은 다음처럼 확인합니다.

export -p

외부 프로그램이 받는 환경은 env 또는 printenv로 확인할 수 있습니다.

printenv PROJECT_MODE
env | grep '^PROJECT_MODE='

변수의 export 속성만 제거하고 값은 현재 셸에 남기려면 Bash에서 export -n을 사용할 수 있습니다.

export -n PROJECT_MODE

값 자체를 제거하려면 unset을 사용합니다.

unset PROJECT_MODE

비밀번호와 API 키도 export하면 안전할까?

환경변수는 비밀 저장소가 아닙니다. 소스코드에 키를 직접 넣는 것보다 분리하기 쉽지만, 같은 권한으로 실행되는 프로세스, 디버깅 도구, 오류 수집, 프로세스 덤프, CI 로그 또는 잘못 작성된 진단 코드에 노출될 수 있습니다. 자식 프로세스는 export된 비밀을 상속하므로 필요 이상으로 넓게 전달하지 않는 것이 좋습니다.

 

한 프로그램만 값을 필요로 한다면 전역적으로 오래 export하기보다 범위를 좁힐 수 있습니다.

API_TOKEN='temporary-value' ./deploy.sh

운영 환경에서는 클라우드 비밀 관리 서비스, Vault, Kubernetes Secret과 짧은 수명의 자격증명 같은 전용 방식을 검토해야 합니다. 명령줄에 비밀을 직접 쓰는 방식 역시 셸 기록 등에 남을 수 있으므로 안전한 주입 경로를 사용해야 합니다.

 

또한 sudo는 보안 정책에 따라 환경변수를 제거하거나 제한할 수 있습니다. 부모 셸에서 export했다고 해서 sudo로 실행한 명령이 모든 값을 그대로 받는다고 가정하면 안 됩니다.

자주 하는 오해 네 가지

1. “대문자로 쓰면 환경변수다”

아닙니다. 대문자는 관례일 뿐입니다. MY_VALUE=test는 대문자여도 export하지 않으면 일반 셸 변수입니다.

2. “export는 값을 복사하는 명령이다”

핵심은 변수에 export 속성을 붙이는 것입니다. 이후 값을 바꾸면 새 값이 다음 자식 프로세스의 환경에 전달됩니다.

export LEVEL=one
LEVEL=two
bash -c 'printf "%s\n" "$LEVEL"'
# two

3. “자식이 바꾸면 부모도 바뀐다”

아닙니다. 상속은 부모에서 자식 방향입니다. 자식의 변경은 부모 셸로 자동 역전파되지 않습니다.

4. “export하면 재부팅 뒤에도 남는다”

아닙니다. export는 현재 셸의 생명주기 안에서 적용됩니다. 영구 설정은 셸 시작 파일, 서비스 관리자, 컨테이너 설정 또는 배포 플랫폼의 환경 설정에 따로 기록해야 합니다.

실무에서는 이렇게 선택하면 된다

  • 셸 스크립트 내부 계산에만 쓰면 VAR=value
  • 이후 실행할 여러 프로그램이 읽어야 하면 export VAR=value
  • 한 명령에만 잠시 주려면 VAR=value command
  • 현재 셸 설정을 바꾸는 스크립트라면 source script.sh
  • 재접속 후에도 필요하면 올바른 셸 시작 파일이나 서비스 설정에 기록
  • 비밀값이라면 전달 범위를 최소화하고 전용 비밀 관리 수단 검토

결국 export의 의미는 “변수를 더 강하게 선언한다”가 아닙니다. 현재 셸에만 있던 이름과 값을 이후 실행되는 자식 프로세스의 환경에도 실어 보낸다는 뜻입니다. 이 한 문장을 기억하면 .env, Docker, Python, Node.js, CI/CD에서 환경변수가 보이거나 보이지 않는 이유를 훨씬 쉽게 추적할 수 있습니다.

참고 자료