- 소프트웨어 개발에서 환경 변수는 매우 오래된 방식임
- 환경 변수는 부모 프로세스에서 자식 프로세스로 상속되는 구조를 가짐
- Linux에서는 execve 시스템 콜을 통해 환경 변수가 전달됨
- Bash, glibc, Python 등은 환경 변수를 각각 다르게 내부적으로 저장 및 처리함
- 이름 규칙 등은 느슨하지만, POSIX 표준 에서 지켜야 할 최소한의 규정이 존재함
환경 변수란 무엇인가
프로그래밍 언어는 빠르게 발전했음에도 운영체제가 제공하는 프로세스 실행 기반 구조, 특히 환경 변수 부분은 변화가 거의 없는 상태임
애플리케이션 실행 시 별도 파일이나 IPC 없이 런타임 파라미터를 전달하려면, 환경 변수 기반 인터페이스를 쓸 수밖에 없는 현실임
환경 변수는 네임스페이스도 없고, 타입도 없는, 플랫한 문자열 딕셔너리 역할을 함
환경 변수 생성 및 전달 구조
- 환경 변수는 부모 프로세스에서 자식 프로세스로 상속되는 구조임
- Linux에서는 execve 시스템 콜이 실행 파일, 인자, 그리고 환경 변수 배열(envp)을 인자로 받음
- 실행 명령 예시: ls -lah라면
- filename: /usr/bin/ls
- argv: ['ls', '-lah']
- envp: ['PATH=...','USER=...']
- 거의 모든 도구(Bash, Python의 subprocess.run, C 라이브러리 execl 등)는 환경 변수를 그대로 넘겨줌
- 예외적으로, login과 같은 일부 도구는 새로운 환경을 구성함
환경 변수의 저장 위치와 내부 처리
- 커널은 환경 변수들을 스택에 null-terminated string 형태로 저장함
- 각 언어 및 쉘의 환경 변수 저장 방식
-
Bash: 스택 구조의 해시맵(딕셔너리)로 관리
- 함수 호출마다 로컬 스코프의 맵 생성
-
export된 변수만 자식 프로세스에 전달
- 로컬 변수도 export 가능함
-
glibc(C 라이브러리) : 동적 배열 구조의 environ을 putenv, getenv를 통해 관리
- 배열 구조라 조회/변경 모두 선형 시간 복잡도를 가짐
-
Python: 내부에서 os.environ으로 딕셔너리처럼 제공하나,
실상은 C 라이브러리의 environ 배열과 연동됨
-
os.environ 값 변경 시 os.putenv 호출되어 C라이브러리에도 반영
- 반대 방향은 동기화되지 않으므로 일방향성 존재
환경 변수의 포맷과 허용 범위
- Linux 커널과 glibc는 환경 변수 포맷에 매우 관대함
- 동일 이름이 중복되어 여러 값이 존재 가능
-
= 없이도 등록 가능하고, 이모지 등 특수문자도 제한 없음
- 가용 사이즈 제한
- 개별 변수: 128 KiB (보통 x64 환경)
- 전체 합계: 2 MiB (명령행 인자와 공유)
- 이는 초기 환경 변수가 스택에 저장됨에 따라 제한 설정
환경 변수의 특이점 및 에지 케이스
- Bash는 이상한 환경 변수(중복, = 없는 항목 등)는 dedup 및 무시
- 변수 이름에 공백이 있으면 Bash는 이름 참조 불가하나, 여전히 자식 프로세스에 전달 가능
- 예: Nushell, Python 등은 공백 이름 변수 생성 가능
- Bash는 이런 항목을 별도 해시맵(invalid_env)에 저장하여 관리
표준(standard) 환경 변수 포맷 및 이름 규칙
-
POSIX 표준은 이름에 등호(=)만 없으면 변수로 인정
- 공식 권장: 이름은 대문자, 숫자, 언더스코어만 허용(맨 앞은 숫자 불가)
- 소문자 변수는 애플리케이션 전용 네임스페이스용 용도
- 표준 도구는 대문자만 사용하지만, 소문자 변수 사용도 허용
- 실제로는 개발자들이 ALL_UPPERCASE 방식으로 주로 네이밍
실용적 권장 사항
- 변수 이름은 정규식 ^[A-Z_][A-Z0-9_]*$ 사용, 값은 UTF-8
- 예외나 호환성 걱정 시 POSIX의 Portable Character Set(ASCII) 사용 권장
결론 및 마무리
- 환경 변수 구조와 실제 동작에 대해 파헤치는 경험 자체가 독특하고 즐거움